Linux C :线程操作和线程同步的多线程并发编程
? ? ? 在這之前可以先看看這邊文章了解線程概念,信號量,條件變量,死鎖、管程等概念
https://blog.csdn.net/superSmart_Dong/article/details/116668370
? ? ? 與進程相比,線程的創建和上下文切換更快。一個進程可以有多個線程,而這些線程都可以訪問自身進程的所有資源。而進程的切換,則涉及到用戶態轉內核態的過程,原先的內存資源可能也要從外存中重新換頁換回內存里。進程的創建也需要重新分配內存和構建頁表等相關數據結構,而線程于進程公用同一個內存空間,除了重新初始化棧之外沒有其他復雜的操作。因此,線程的相應速度也比會比進程快。多個線程則對應有多個執行路徑,當一個線程被阻塞時,同進程下的其他線程仍然會在后臺進行計算。進程之間必須通過進程間通信(IPC)來交換數據或者把共享資源納入到進程的地址空間中,而同進程下的線程之間可以直接相互訪問。
? ? ?當然線程操作需要考慮線程的同步問題。通常許多庫函數可能是線程不安全的,通常情況下,依賴于全局變量或者靜態變量的函數,如果沒有特殊處理都存在線程不安全問題。
目錄
一、線程操作
二、利用多線程進行快速排序
三、線程同步——互斥量、鎖
四、線程同步-信號量
?五、線程同步-屏障
六、線程同步-條件變量
七、用條件變量解決生產者和消費者問題
一、線程操作
? ? ?幾乎所有的操作系統都支持 POSIX Pthread 標準應用程序編程接口,在Linux中需要引入的頭文件是 <pthread.h>
1)創建線程
int pthread_create(pthread_t *pthread_id ,pthread_attr_t *attr,void*(*func)(void*) , void * arg)
- pthread_id 是線程id,數據類型是pthread_t,在POSIX是一種不透明類型,它的id是操作系統中唯一的。可以通過pthread_self()函數來獲取當前線程的id。在Linux中,pthread_t被定義為無符號長整型,打印id的為? %lu
- ? ? attr 是pthread_attr_t的數據類型,在POSIX是一種不透明類型。它用來指定需要創建線程的屬性。通常如果選擇NULL則系統會給線程一個默認的推薦配置。該參數的初始化要稍微繁瑣些,主要的使用步驟為。
- ? ? func? 是線程的執行函數體地址
- ? ? arg? ?是線程的執行函數體的函數參數。
2)線程id的比較
? ? int pthread_equal( pthread_t? t1, pthread_t? t2);
? ?比較兩個線程id是否相等,相等則返回0,否則為非0。
3)線程終止
? ?int pthread_exit(void *status);
? ?當線程函數執行結束后線程即終止。或者可以調用上述函數顯式地終止當前線程。其中,status時線程的退出狀態。操作系統規范通常是0表示正常終止,非0表示異常終止
4)線程關聯?join
? int pthread_join(pthread_t thread_id,void **status_ptr);
? 當前進程等待其他線程的終止,其中線程的終止狀態值通過status_ptr引用入參返回。
二、利用多線程進行快速排序
? ? ? ?快排原理就不細說了,大致就是每次排序把其中一個元素通過頭尾比大小交換到正確的位置上,之后分成左右兩邊進行遞歸再排序。 因此,當左右兩邊的排序并不是相互依賴的,因此可以通過線程的方式并行排序從而提高排序效率。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> //快排函數需傳入的參數,數組上下限 typedef struct {int upperbound;int lowerbound; }PARM;#define N 11 int A[N] = {5,1,6,4,7,2,9,9,8,0,3};int print(){int i ;printf("[ ");for (int i=0 ; i< N ; i++){printf("%d ",A[i]);}printf(" ]\n");return 0; }void *qsort(void *aptr){// ap 用來接收傳入的參數,aleft 和aright 用來初始化新建線程的入參PARM *ap ,aleft,aright;//排序元素,排序元素位置, 排序時用的2個索引變量,和一個臨時變量int pivot,pivotIndex,left,right,temp;// 接收入參數組的上下限int upperbound, lowerbound;pthread_t me,leftThread,rightThread;me = pthread_self();ap = (PARM *) aptr;upperbound = ap->upperbound;lowerbound = ap->lowerbound;pivot = A[upperbound];left = lowerbound -1 ;right = upperbound ;if ( lowerbound >= upperbound){pthread_exit(NULL);}//單次快速排序 while(left <right ){do { left++; } while( A[left] < pivot);do {right --;} while ( A[right] >pivot);if( left < right){temp = A[left];A[left] = A[right];A[right] = temp ;}}print();pivotIndex = left ;temp = A [pivotIndex];A[pivotIndex] = pivot;A[upperbound] =temp ;//單次快排結束 .....// 啟動線程遞歸快排aleft.upperbound = pivotIndex -1 ;aleft.lowerbound = lowerbound ;aright.lowerbound = pivotIndex +1 ;aright.upperbound = upperbound ;printf("%lu : create threads \n ",me );pthread_create(&leftThread,NULL,qsort,(void*)&aleft);pthread_create(&rightThread,NULL,qsort,(void*)&aright);// 等待左右兩個線程執行完再繼續執行pthread_join(leftThread,NULL);pthread_join(rightThread,NULL);printf("%lu: join with left & right threads \n",me); }int main(int argc , char*argv[]){PARM arg;int i, *array ;pthread_t me ,thread;me = pthread_self();printf("main %lu : unsorted array = ",me );print();arg.upperbound = N -1 ;arg.lowerbound = 0 ;printf("%lu : create threads for qsort \n ",me );pthread_create(&thread,NULL,qsort,(void*)&arg);pthread_join(thread,NULL);printf("%lu :finish \n ",me );print();return 0; }命令? ?g++? pthreadqsort.c -pthread? 進行編譯:輸出結果如下圖:
三、線程同步——互斥量、鎖
? ? ?互斥量、鎖結構體實際上由?int型成員,所有者和阻塞隊列組成。?int型成員當值為0則代表這塊資源沒被占用,允許被訪問,值為1時代表資源被占用,不能再被訪問。
? ? 臨界區概念:臨界區是指要訪問共享資源的那段代碼塊。這里的共享資源指系統資源、全局變量等全局資源。落到機器層面上,代碼塊指的時機器語言上的代碼塊。對于臨界區的保護訪問,通常的方式就是在臨界區外圍加上互斥機制。
? ? 互斥變量用 pthread_mutex_t 聲明,初始化的方法有兩種..基本操作函數有4鐘
/*******有兩種方式初始化********************/ pthread_mutex_t m =PTHREAD_MUTEX_INITIALIZER; //靜態方法初始化 pthread_mutex_init(ptherad_mutex_t *m , pthread_mutexattr_t *attr); //動態方法 /*********鎖的操作函數**********************/ int pthread_mutex_lock(ptherad_mutex_t *m ); //上鎖,如果已被上鎖則阻塞線程 int pthread_mutex_unlock(ptherad_mutex_t *m ); //解鎖,上鎖的變量只能由當前線程解鎖 int pthread_mutex_trylock(ptherad_mutex_t *m ); //嘗試上鎖,如果已被上鎖則繼續執行 int pthread_mutex_destory(ptherad_mutex_t *m ); //銷毀鎖,如果是動態分配的互斥量,所有線程都完成后可能會被自動銷毀。? ?看看如下代碼
#include <stdio.h> #include <stdlib.h> #include <pthread.h> int i; pthread_mutex_t *m; void* func (void *arg){pthread_mutex_lock(m);/*****臨界區↓************/int w = i%2;i += w+1;/*****臨界區↑************/pthread_mutex_unlock(m); } int main(int argc,char*argv[]){pthread_t thread_1 , thread_2;i = 0;void * status;m = (pthread_mutex_t * )malloc(sizeof(pthread_mutex_t));pthread_mutex_init(m,NULL);pthread_create(&thread_1 ,NULL,func,NULL);pthread_create(&thread_2 ,NULL,func,NULL);pthread_join(thread_1,&status);pthread_join(thread_2,&status);printf("i= %d",i); }在臨界區之外加了同步之后,輸出的結果就是i=3,如果沒給臨界區上鎖,那么線程可能執行到一一半就到另外的線程繼續執行。輸出的結果除了i=3之外,還有可能會 i=2.
四、線程同步-信號量
? ? ? ?信號量是一種數據結構,一個用int 型數據用來記錄剩余資源數,另外一個成員用來記錄當資源不足時,用鏈表表示的一個阻塞線程的隊列。最經典的信號量操作就是P(),和V()用來實現線程的同步和互斥。P 和 V 操作都是原子操作,其大致偽代碼定義如下.
struct sempahore{int value;struct process *queue; //阻塞隊列 }s;P(sempahore *s){s->value--;if(s->value <0){BLOCK(s); //阻塞當前線程并加入到信號量的阻塞隊列中} }V(sempahore *s){s->value++;if(s->value <=0){SIGNAL(s); //從線程中阻塞隊列中出隊并喚醒出隊的線程} }由于信號量不是POSIX的標準部分,但是Linux 還是支持信號量的一些操作
#include <semaphore.h> //初始化信號量,并指定信號量的類型pshared=0表示局部信號量否則可以在多線程間共享,設置可使用資源值value int sem_init(sem_t *sem,int pshared,unsigned int value); int sem_destory(sem_t *sem) //銷毀信號量 int sem_post(sem_t *sem); //等同于 V()函數 int sem_wait(sem_t *sem); //等同于 P()函數?五、線程同步-屏障
? ? ? 在pthread_join()中,允許某個線程等待其他線程終止再繼續執行.假如只是為了線程同步到達某個指定代碼塊的開始區域,而不希望終止線程,就可以用屏障。例如,希望每個線程都按順序執行A,B模塊代碼。假如希望線程一同執行完A模塊再同時開始執行B模塊,不希望創建線程序列專門執行單獨的模塊來實現同步,那么屏障就是一個很好的選擇。他可以節省創建過多的線程造成較大的系統開銷,還可以再保持線程活躍的情況下同步其他線程的執行情況。
#include <pthread.h>pthread_barrier_t barrier; //聲明//屏障初始化, 第一個是pthread_barrier_t的地址,第二個是設置屬性,第3個參數是等待nthreads個線程到達,則把屏障所有阻塞的線程變成就緒態。 pthread_barrier_init(&barrier , NULL , nthreads);//讓屏障的計數減1并阻塞當前線程,如果共wait了nthreads次,則線程繼續執行 pthread_barrier_wait(&barrier)六、線程同步-條件變量
? ? ? 條件變量通常和互斥量配合使用。條件變量的成員用來記錄阻塞線程數(初始值為0)和另一個成員是阻塞線程隊列。類似于管程原理,最多只能有1個線程同時訪問條件變量,因此訪問條件變量時,需要在訪問條件變量的外圍加上互斥量鎖。即
#include <pthread.h>pthread_mutex_t con_mutex; //互斥鎖 pthread_cond_t con ; //條件變量func(){pthread_mutex_lock(&mutex); /*****************臨界區 ******************/ pthread_cond_wait(&con,&mutex); pthread_mutex_unlock(&mutex);}在Linux中,條件變量的操作方法
pthread_cond_init(&con,NULL); //初始化條件變量 //阻塞當前線程到條件變量的阻塞隊列,之后釋放鎖并sechedule(),使阻塞線程讓出CPU使用權,使將來的喚醒的線程再獲取鎖,繼續執行接下來的代碼 pthread_cond_wait(&con,&mutex); //如果當前有阻塞線程,把阻塞線程喚醒,阻塞線程數減1 pthread_cond_signal(&con); //喚醒所有阻塞線程 pthread_cond_broadcast(&con);條件變量通常需要多個不同功能的線程進行。因為自身線程如果執行了pthread_cond_wait,那么只能夠由其他線程執行pthread_cond_signal,才能把自己喚醒,否則將陷入無限等待中。
七、用條件變量解決生產者和消費者問題
#include <stdio.h> #include <stdlib.h> #include <pthread.h>#define NBUF 5 #define N 10int buf [NBUF]; int head,tail; int data; pthread_mutex_t mutex; pthread_cond_t empty ,full;int init(){head = tail =data = 0;pthread_mutex_init(&mutex,NULL);pthread_cond_init(&full,NULL);pthread_cond_init(&empty , NULL); }void *producer(void *){int i ;pthread_t me = pthread_self();for( i = 0 ; i< N ;i++){pthread_mutex_lock(&mutex); if (data == NBUF ){printf("producer %lu :all buf FULL ,waiting\n",me);pthread_cond_wait(&empty , &mutex);}buf [head ++] = i+1;head %= NBUF;data ++ ; printf("producer %lu :data =%d value=%d \n",me, data ,i+1);pthread_mutex_unlock(&mutex);pthread_cond_signal (&full);}printf("producer %lu : exit \n",me); }void * consumer(void *){int i,c ;pthread_t me = pthread_self();for( i=0 ; i<N ;i++){pthread_mutex_lock(&mutex);if (data ==0){printf("consumer %lu :all buf empty ,waiting\n",me);pthread_cond_wait(&full , &mutex);}c = buf[tail ++];tail %=NBUF;data -- ; printf("consumer %lu :data =%d value=%d \n",me, data,c);pthread_mutex_unlock(&mutex);pthread_cond_signal (&empty);} }int main(){pthread_t pro,con;init();pthread_create(&pro,NULL,producer,NULL);pthread_create(&con,NULL,consumer,NULL);printf("main join with thread\n"); pthread_join(pro,NULL);pthread_join(con,NULL);printf("main exit \n"); }g++? condconsum.c? -pthread? ?命令進行編譯
執行 a.out 的結果之一如下
總結
以上是生活随笔為你收集整理的Linux C :线程操作和线程同步的多线程并发编程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux C:管道的实现原理,命名管道
- 下一篇: Linux C: 定时器及时钟服务