Linux c語言 多線程編程學習


概念

  • 在線程概念出現以前,為了減小進程切換的開銷,操作系統設計者逐漸修正進程的概念,逐漸允許將進程所占有的資源從其主體剝離出來,允許某些進程共享一部分資源,例如文件、信號,數據內存,甚至代碼,這就發展出輕量進程的概念。
  • 一個進程至少需要一個線程作為它的指令執行體,進程管理着資源(比如cpu、內存、文件等等),而將線程分配到某個cpu上執行。一個進程當然可以擁有多個線程,此時,如果進程運行在SMP機器上,它就可以同時使用多個cpu來執行各個線程,達到最大程度的並行,以提高效率;
  • 多線程:多線程是指程序中包含多個執行流,即在一個程序中可以同時運行多個不同的線程來執行不同的任務,也就是說允許單個程序創建多個並行執行的線程來完成各自的任務。
優點
  • 啟動一個線程所花費的空間遠遠小於啟動一個進程所花費的空間,線程間彼此切換所需的時間也遠遠小於進程間切換所需要的時間
  • 同一進程下的線程之間共享數據空間,線程間方便的通信機制
  • 提高應用程序響應。
  • 操作系統會保證當線程 數不大於CPU數目時,不同的線程運行於不同的CPU上。
  • 一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利於理解和修改。
LinuxThreads實現機制

LinuxThreads是目前Linux平台上使用最為廣泛的線程庫。LinuxThreads,它使用(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND)參數來調用clone()創建”線程”,表示共享內存、共享文件系統訪問計數、共享文件描述符表,以及共享信號處理方式。它所實現的就是基於核心輕量級進程的”一對一”線程模型,在核外實現的線程又可以分為”一對一”、”多對一”兩種模型,前者用一個核心進程(也許是輕量進程)對應一個線程,將線程調度等同於進程調度,交給核心完成,而后者則完全在核外實現多線程,調度也在用戶態完成。線程之間的管理在核外函數庫中實現。”一對一”模型的好處之一是線程的調度由核心完成了,而其他諸如線程取消、線程間的同步等工作,都是在核外線程庫中完成的。

編譯需要注意:

在編譯C的多線程時候,Linux系統下的多線程遵循POSIX線程接口,稱為pthread。一方面必須指定Linux C語言線程庫多線程庫pthread,連接時需要使用庫libpthread.a,才可以正確編譯(例如:gcc test.c -o test -lpthread);另一方面要包含有關線程頭文件#include <pthread.h>。 
Linux下使用eclipse時,Makefiel文件可以由eclipse自動生成,可以通過修改它的工程配置來改變Makefile的參數 
如:在使用線程操作時,需要添加-lpthread才能編譯通過,在Link flags處添加編譯選項:-lpthread。

多線程基本操作

1、線程的創建與等待

  • int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

    作用:用來創建一個線程 
    thread為指向線程標識符的指針,attr用來設置線程屬性,start_routine是線程運行函數的起始地址,arg是運行函數的參數。 
    當進程第一次調用pthread_create()創建一個線程的時候就會創建(__clone())並啟動管理線程。管理線程的工作主要就是監聽管道讀端,並對從中取出的請求作出反應。
void function(void * t); //函數的傳入參數為 void* 類型
pthread_t id; // 定義一個線程id
retv = pthread_create(&id, NULL, (void*)&function_name, (void*)t); //線程屬性為默認值(后面分析),運行函數起始地址類型必須是 void* ,函數function傳入參數 t 的類型也必須是 void *,傳入多個數據要用結構體
if(retv != 0)
{ 線程創建失敗! }
  • int pthread_join(pthread_t thread, void ** thread_return);

作用:用來等待一個線程的結束 
thread為被等待的線程標識符,thread_return為一個用戶定義的指針,它可以用來存儲被等待線程的返回值。這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。

/*有返回值*/
void * ret;
pthread_join(id,&ret);
/*無返回值*/
pthread_join(id,NULL);
  • void pthread_exit(void * retval);

函數結束了,調用它的線程也就結束了。唯一的參數是函數的返回代碼,只要pthread_join中的第二個參數thread_return不是NULL,這個值將被傳遞給 thread_return。

void * function (void)
{
.....
pthread_exit((void*)retval);
}

pthread.c:
# include<stdio.h>
# include<unistd.h> // 包含sleep();
# include<stdlib.h>
# include<pthread.h>
# include<sched.h> //包含調度函數
 
void pthread1(void);
void* pthread2(void);
void pthread_3(void*);
 
int main(void)
{
pthread_t id1;
pthread_t id2;
pthread_t id3;
pthread_attr_t id1_attr; //定義屬性變量
int retv = 0;
long int t = 55;
void * ret;
 
pthread_attr_init(&id1_attr);
pthread_attr_setscope(&id1_attr, PTHREAD_SCOPE_SYSTEM);
 
retv = pthread_create(&id1,&id1_attr,(void*)&pthread1,NULL); //pthread_create創建線程成功返回0,失敗返回-1。
if(0 != retv)
{
printf("Create pthread error!\n");
return -1;
}
 
pthread_create(&id2,NULL,(void*)&pthread2,NULL);
 
pthread_join(id1,NULL);
pthread_join(id2,&ret); //等待線程id2結束,用ret接收返回值
 
pthread_create(&id3,NULL, (void*)&pthread_3,(void *)t); //創建線程id3,向線程函數傳參數t
pthread_join(id3,NULL);
 
printf("Hello,i am the main process!\n");
printf("return the id2 is: %lu\n", (long int)ret);
 
return 0;
}
 
void pthread1(void)
{
printf("I am the first thread!\n");
return
}
 
void* pthread2(void)
{
long int i = NULL;
 
while(i<10)
{
i++;
}
 
return (void*)i;
//pthread_exit((void*)i); //線程退出結束
}
 
void pthread_3(void * val)
{
printf("t的值為:%lu\n", (long int)val); //void*類型不能直接打印,8字節
return
}

2、線程的屬性

  • pthread_attr_init(&attr);

    作用:初始化線程屬性

  • pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

作用:設置線程綁定狀態 
attr是指向屬性結構的指針,第二個參數是綁定類型,它有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。

示例:

pthread_attr_t attr;
/*初始化屬性值,均設為默認值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&tid, &attr, (void *) my_function, NULL);
  • int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

    作用:修改線程的分離狀態屬性
  • int pthread_attr_getdetazchstate(const pthread_attr_t * attr,int *detachstate);

    作用:獲取線程的分離狀態屬性 
    第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)

  • int pthread_attr_setschedparam(pthread_attr_t *attr,struct sched_param *param);

    作用:設置線程的調度參數

  • int pthread_attr_getschedparam(pthread_attr_t *attr,struct sched_param *param);

    作用:得到線程的調度參數 
    線程的優先級,它存放在結構sched_param中,結構sched_param的子成員sched_priority控制一個優先權值,大的優先權值對應高的優先權。(系統支持的最大和最小優先權值可以用sched_get_priority_max函數和sched_get_priority_min函數分別得到) 
    注:如果不是編寫實時程序,不建議修改線程的優先級。因為,調度策略是一件非常復雜的事情,如果不正確使用會導致程序錯誤,從而導致死鎖等問題。如:在多線程應用程序中為線程設置不同的優先級別,有可能因為共享資源而導致優先級倒置。

  • int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy);

    作用:得到線程的調度策略

  • int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);

    作用:設置線程的調度策略 
    policy為調度策略或指向調度策略的指針。調度策略可能的值是先進先出(SCHED_FIFO)、輪轉法(SCHED_RR),或其它(SCHED_OTHER)。 
    SCHED_FIFO策略允許一個線程運行直到有更高優先級的線程准備好,或者直到它自願阻塞自己。在SCHED_FIFO調度策略下,當有一個線程准備好時,除非有平等或更高優先級的線程已經在運行,否則它會很快開始執行。 
    SCHED_RR(輪循)策略是基本相同的,不同之處在於:如果有一個SCHED_RR 
    策略的線程執行了超過一個固定的時期(時間片間隔)沒有阻塞,而另外的SCHED_RR或SCHBD_FIPO策略的相同優先級的線程准備好時,運行的線程將被搶占以便准備好的線程可以執行。 
    當有SCHED_FIFO或SCHED_RR策賂的線程在一個條件變量上等待或等待加鎖同一個互斥量時,它們將以優先級順序被喚醒。即,如果一個低優先級 的SCHED_FIFO線程和一個高優先織的SCHED_FIFO線程都在等待鎖相同的互斥量,則當互斥量被解鎖時,高優先級線程將總是被首先解除阻塞。

  • int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);

    作用:獲得線程的繼承性

  • int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched);

    作用:設置線程的繼承性 
    繼承性決定調度的參數是從創建的進程中繼承還是使用在 schedpolicy和schedparam屬性中顯式設置的調度信息。Pthreads不為inheritsched指定默認值,因此如果你關心線程 的調度策略和參數,必須先設置該屬性。繼承性的可能值是PTHREAD_INHERIT_SCHED,表示新現成將繼承創建線程的調度策略和參數;或者PTHREAD_EXPLICIT_SCHED(缺省),表示使用在schedpolicy和schedparam屬性中顯式設置的調度策略和參數。


pthread2.c:
# include<stdio.h>
# include<pthread.h>
 
void function(void * data)
{
int i;
for(i=0; i<=2; i++)
if(i == 2)
printf("I'm thread th%ld!\n", (long int)data);
}
 
int main(void)
{
pthread_t th1;
pthread_t th2;
 
/*線程屬性 */
pthread_attr_t attr_th1;
pthread_attr_t attr_th2;
 
long int a=1, b=2;
 
printf("I'm the main prosser!\n");
 
pthread_attr_setscope(&attr_th1, PTHREAD_SCOPE_PROCESS); //非綁定
pthread_attr_setscope(&attr_th2, PTHREAD_SCOPE_SYSTEM); //綁定
 
/*線程屬性初始化*/
pthread_attr_init(&attr_th1);
pthread_attr_init(&attr_th2);
 
/*創建線程*/
pthread_create(&th1, &attr_th1, (void*)&function, (void *)a);
pthread_create(&th2, &attr_th2, (void*)&function, (void *)b);
 
/*等待線程結束*/
pthread_join(th1, NULL);
pthread_join(th2, NULL);
 
return 0;
}

3、互斥鎖

  • int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);

    作用:用來生成一個互斥鎖 
    pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; //生成靜態鎖 
    pthread_mutex_init(); 一般用來生成動態鎖

  • pthread_mutexattr_init(pthread_mutexattr_t * mattr);

    作用:聲明特定屬性的互斥鎖,須調用函數

  • pthread_mutexattr_setpshared(pthread_mutexattr_t *mattr, int pshared);

    作用:設置屬性pshared

  • pthread_mutexattr_getshared(pthread_mutexattr_t *mattr,int *pshared);

    作用:獲取鎖的范圍 
    它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,后者用於同步本進程的不同線程。默認是后者,表示進程內使用鎖

  • pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type);

    作用:設置鎖的類型

  • pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type);

    作用:獲取鎖的類型 
    PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,並通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。 
    PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。 
    PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。 
    PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,並在解鎖后按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。

  • pthread_mutex_lock();

    作用:聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止

  • pthread_mutex_trylock();

    作用:它是函數pthread_mutex_lock的非阻塞版本,當它發現死鎖不可避免時,它會返回相應的信息,程序員可以針對死鎖做出相應的處理。

  • int pthread_mutex_unlock(pthread_mutex_t *mutex);

    作用:解鎖

總結:

1) 只能用於”鎖”住臨界代碼區域 
2) 一個線程加的鎖必須由該線程解鎖. 
鎖幾乎是我們學習同步時最開始接觸到的一個策略,也是最簡單, 最直白的策略.

4、條件變量

  • int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);

    作用:初始化一個條件變量 
    結構pthread_condattr_t是條件變量的屬性結構,用它來設置條件變量是進程內可用還是進程間可用, 默認值是PTHREAD_ PROCESS_PRIVATE,即此條件變量被同一進程內的各個線程使用;PTHREAD_PROCESS_SHARED則為多個進程間各線程公用。

  • int pthread_cond_destroy(pthread_cond_t *cond);

    作用:注銷條件變量

  • int pthread_cond_wait(pthread_cond_t * cond, pthread_mutex_t * mutex);

    作用:條件等待 ,mutex用於保護條件 
    注:受signal激活后,wait先解鎖mutex,pthread_cond_wait執行最后一個操作:重新鎖定mutex;蘇醒之前不會消耗CPU周期

  • int pthread_cond_signal(pthread_cond_t * cond);

    作用:條件激活 
    注:在條件滿足時,給正在等待的線程發送信息,喚醒該線程,wait的線程繼續執行。


pthread3.c:
#include<stdio.h>
# include<pthread.h>
#include<unistd.h>
# include<malloc.h>
 
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
struct node {
int n_number;
struct node *n_next;
 
} *head = NULL;
 
static void *thread_func(void)
{
struct node *p = NULL;
 
while (1)
{
pthread_mutex_lock(&mtx);
while (head == NULL)
{
pthread_cond_wait(&cond, &mtx);
}
p = head;
head = head->n_next;
printf("Got %d from front of queue\n", p->n_number);
free(p);
p = NULL;
pthread_mutex_unlock(&mtx); //臨界區數據操作完畢,釋放互斥鎖
}
 
return 0;
 
}
 
int main(void)
{
pthread_t tid;
int i;
struct node *p;
pthread_create(&tid, NULL, (void*)&thread_func, NULL);
for (i = 0; i < 10; i++)
{
p = (struct node *)malloc(sizeof(struct node));
p->n_number = i;
pthread_mutex_lock(&mtx);
p->n_next = head;
head = p;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mtx); //解鎖
sleep(1);
 
}
 
printf("head'data is %d\n", head->n_number);
printf("thread 1 wanna end the line.So cancel thread 2.\n");
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("p'data is %d.\n", p->n_number);
printf("All done -- exiting\n");
return 0;
}

5、信號量

信號無需由同一個線程來獲取和釋放,因此信號可用於異步事件通知,如用於信號處理程序中。同時,由於信號包含狀態,因此可以異步方式使用,而不用象條件變量那樣要求獲取互斥鎖。但是,信號的效率不如互斥鎖高。缺省情況下,如果有多個線程正在等待信號,則解除阻塞的順序是不確定的。使用頭文件#include <semaphore.h>

  • int sem_init (sem_t * sem, int pshared, unsigned int value);

    作用:對由sem指定的信號量進行初始化。 
    pshared為0時,表示這個信號量只是當前進程中的信號量(該進程內的 所有線程使用),不為0,這個信號量可能可以在兩個進程中共享;value給出了信號量的初始值 
    注: 
    (1)多個線程決不能初始化同一個信號。 
    (2)不得對其他線程正在使用的信號重新初始化。

  • int sem_wait(sem_t *sem);

    作用:用於接受信號 
    當sem>0時就能接受到信號,然后將sem– ;sem為0的信號量調用sem_wait,這個函數將會等待直到有其它線程使它不再是0為止。

  • int sem_post(sem_t *sem);

    作用:給信號量的值加1 。注:如果所有線程均基於信號阻塞,則會對其中一個線程解除阻塞。

  • int sem_destroy(sem_t *sem);

    作用:銷毀與sem所指示的未命名信號相關聯的任何狀態。釋放內存空間


pthread4.c:
#include<stdio.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <semaphore.h>
#define BUFSIZE 4
#define NUMBER 8
 
int sum_of_number=0;
 
/* 可讀 和 可寫資源數*/
sem_t write_res_number;
sem_t read_res_number;
 
/* 循環隊列 */
struct recycle_buffer
{
int buffer[BUFSIZE];
int head,tail;
}re_buf;
 
/* 用於實現臨界區的互斥鎖,我們對其初始化*/
pthread_mutex_t buffer_mutex=PTHREAD_MUTEX_INITIALIZER;
 
static void producer(void)
{
//int * num_of_w = NULL;
int i;
for(i=0;i<=NUMBER;i++)
{
/* 減少可寫的資源數 */
sem_wait(&write_res_number);
/* 進入互斥區 */
pthread_mutex_lock(&buffer_mutex);
/*將數據復制到緩沖區的尾部*/
re_buf.buffer[re_buf.tail]=i;
re_buf.tail=(re_buf.tail+1)%BUFSIZE;
printf("procuder %lu write %d.\n", pthread_self(),i); //pthread_self();獲得線程自身的ID。pthread_t的類型為unsigned long int
/*離開互斥區*/
pthread_mutex_unlock(&buffer_mutex);
/*增加可讀資源數*/
sem_post(&read_res_number);
 
//sem_getvalue(&write_res_number, num_of_w);
//printf("%d\n", *num_of_w);
}
/* 線程終止,如果有線程等待它們結束,則把NULL作為等待其結果的返回值*/
return;
 
}
 
static void consumer(void)
{
int i,num;
for(i=0;i<=NUMBER;i++)
{
/* 減少可讀資源數 */
sem_wait(&read_res_number);
/* 進入互斥區*/
pthread_mutex_lock(&buffer_mutex);
/* 從緩沖區的頭部獲取數據*/
num = re_buf.buffer[re_buf.head];
re_buf.head = (re_buf.head+1)%BUFSIZE;
printf("consumer %lu read %d.\n", pthread_self(),num);
/* 離開互斥區*/
pthread_mutex_unlock(&buffer_mutex);
sum_of_number+=num;
/* 增加可寫資源數*/
sem_post(&write_res_number);
}
/* 線程終止,如果有線程等待它們結束,則把NULL作為等待其結果的返回值*/
return;
}
 
int main(void)
{
/* 用於保存線程的線程號 */
pthread_t p_tid;
pthread_t c_tid;
int i;
re_buf.head=0;
re_buf.tail=0;
for(i=0;i<BUFSIZE;i++)
re_buf.buffer[i] =0;
/* 初始化可寫資源數為循環隊列的單元數 */
sem_init(&write_res_number,0,BUFSIZE); // 這里限定了可寫的bufsize,當寫線程寫滿buf時,會阻塞,等待讀線程讀取
/* 初始化可讀資源數為0 */
sem_init(&read_res_number,0,0);
/* 創建兩個線程,線程函數分別是 producer 和 consumer */
/* 這兩個線程將使用系統的缺省的線程設置,如線程的堆棧大小、線程調度策略和相應的優先級等等*/
pthread_create(&p_tid,NULL,(void*)&producer,NULL);
pthread_create(&c_tid,NULL,(void*)&consumer,NULL);
/*等待兩個線程完成退出*/
pthread_join(p_tid,NULL);
pthread_join(c_tid,NULL);
 
printf("the head is : %d\n", re_buf.head);
printf("the tail is : %d\n", re_buf.tail);
printf("The sum of number is %d\n",sum_of_number);
 
return 0;
}

下面是一個死鎖的例子:

# include<stdio.h>
# include<pthread.h>
# include<unistd.h>
 
static pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
void *function1(void *val)
{
int i = 1;
while(i<3)
{
pthread_cond_wait(&cond, &mutex1);
 
pthread_mutex_lock(&mutex1);
i++;
printf("%lu\n", (long int)val);
pthread_mutex_unlock(&mutex1);
}
pthread_exit(NULL);
}
 
void *function2(void *val)
{
int i = 0;
 
while(i<3)
{
pthread_mutex_lock(&mutex1);
 
printf("%lu\n", (long int)val);
pthread_cond_signal(&cond);
 
i++;
pthread_mutex_unlock(&mutex1);
sleep(1);
}
 
pthread_exit(NULL);
}
 
int main(void)
{
pthread_t id1;
pthread_t id2;
 
long int t1 = 55;
long int t2 = 66;
 
pthread_create(&id1, NULL, function1, (void*)t1);
pthread_create(&id2, NULL, function2, (void*)t2);
 
pthread_join(id1, NULL);
 
return 0;
}
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM