傳送門:Linux多線程編程實例解析 .?
????????????????????linux多線程編程——同步與互斥 .
????
????? ?傳統多任務操作系統中一個可以獨立調度的任務(或稱之為順序執行流)是一個進程。每個程序加載到內存后只可以唯一地對應創建一個順序執行流,即傳統意義的進程。每個進程的全部系統資源是私有的,如虛擬地址空間,文件描述符和信號處理等等。使用多進程實現多任務應用時存在如下問題:
1)任務切換,即進程間上下文切換,系統開銷比較大。(虛擬地址空間以及task_struct 都需要切換)
2)多任務之間的協作比較麻煩,涉及進程間通訊。(因為不同的進程工作在不同的地址空間)
所以,為了提高系統的性能,許多操作系統規范里引入了輕量級進程的概念,也被稱為線程。
?
一、線程基礎
????? 通常線程指的是共享相同地址空間的多個任務。線程最大的特點就是在同一個進程中創建的線程共享該進程的地址空間;但一個線程仍用task_struct 來描述,線程和進程都參與統一的調度。所以,多線程的好處便體現出來:
1)大大提高了任務切換的效率;因為各線程共享進程的地址空間,任務切換時只要切換task_struct 即可;
2)線程間通信比較方便;因為在同一塊地址空間,數據共享;
當然,共享地址空間也會成為線程的缺點,因為共享地址空間,如果其中一個線程出現錯誤(比如段錯誤),整個線程組都會崩掉!
???? Linux之所以稱呼其線程為LWP( Light Weight Process ),因為從內核實現的角度來說,它并沒有為線程單獨創建一個結構,而是繼承了很多進程的設計:
1)繼承了進程的結構體定義task_struct ;
2)沒有專門定義線程ID,復用了PID;
3)更沒有為線程定義特別的調度算法,而是沿用了原來對task_struct 的調度算法。
在最新的Linux內核里線程已經替代原來的進程稱為調度的實際最小單位。
原來的進程概念可以看成是多個線程的容器,稱之為線程組;即一個進程就是所有相關的線程構成的一個線程組。傳統的進程等價于單線程進程。
每個線程組都有自己的標識符 tgid (數據類型為 pid_t ),其值等于該進程(線程組)中的第一個線程(group_leader)的PID。
?
1、創建線程
?pthread_create()函數描述如下:
| 所需頭文件 | #include <pthread.h> |
| 函數原型 | int pthread_create(pthread_t *thread,const pthread_attr_t *attr, ?????????????????????????????? void *(* routine)(void *), void *arg) |
| 函數參數 | thread :創建的線程 attr :指定線程的屬性,NULL表示使用缺省屬性 routine :線程執行的函數 arg :傳遞給線程執行的函數的參數 |
| 函數返回值 | 成功: 0 出錯: -1 |
1)這里routine 是回調函數(callback),其函數類型由內核來決定,這里我們將其地址傳給內核;這個函數并不是線程創建了就會執行,而是只有當其被調度到cpu上時才會被執行;具體回調函數的講解,移步Linux C 函數指針應用---回調函數 .;
2)arg 是線程執行函數的參數,這里我們將其地址穿進去,使用時需要先進行類型轉換,才能使用;如果參數不止一個,我們可以將其放入到結構體中;
?
2、pthread_join () 函數
其函數描述如下:
| 所需頭文件 | #include <pthread.h> |
| 函數原型 | int thread_join(pthread_t thread, void? ** value_ptr) |
| 函數參數 | thread :要等待的線程 value_ptr :指針 *value_ptr 指向線程返回的參數 |
| 函數返回值 | 成功: 0 出錯: -1 |
這里,我們可以看到 value_ptr 是個二級指針,其是出參,存放的是線程返回參數的地址;
?
3、pthread_exit 函數
其函數描述如下:
| 所需頭文件 | #include <pthread.h> |
| 函數原型 | int pthread_exit(void *value_ptr) |
| 函數參數 | value_ptr :線程退出時返回的值 |
| 函數返回值 | 成功:0 出錯:-1 |
和進程中的exit() 、wait()一樣,這里pthread_join 與 pthread_exit 是工作在兩個線程之中;
?
下面看一個實例:
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stdlib.h>?? #include?<string.h>?? #include?<pthread.h>?? ?? char?message[32]?=?"Hello?World!";?? void?*thread_function(void?*arg);?? ?? int?main()?? {?? ????pthread_t?a_thread;?? ????void?*thread_result;?? ?? ????if(pthread_create(&a_thread,NULL,thread_function,(void?*)message)?<?0)?? ????{?? ????????perror("fail?to?pthread_create");?? ????????exit(-1);?? ????}?? ?? ????printf("waiting?for?thread?to?finish\n");?? ????if(pthread_join(a_thread,&thread_result)?<?0)?? ????{?? ????????perror("fail?to?pthread_join");?? ????????exit(-1);?? ????}?? ?? ????printf("Message?is?now?%s\n",message);?? ????printf("thread_result?is?%s\n",(char?*)thread_result);?? ????return?0;?? }?? ?? void?*thread_function(void?*arg)?? {?? ????printf("thread_function?is?running,argument?is?%s\n",(char?*)arg);?? ????strcpy(message,"marked?by?thread");?? ????pthread_exit("Thank?you?for?the?cpu?time");?? }??
編譯
[cpp]?view plaincopy
fs@ubuntu:~/qiang/thread/0107$?gcc?-o?thread?thread.c?-lpthread?? fs@ubuntu:~/qiang/thread/0107$???
線程通過第三方的線程庫來實現,所以這里要 -lpthread ,-l 是鏈接一個庫,這個庫是pthread;
執行結果如下:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/thread/0107$?./thread??? waiting?for?thread?to?finish?? thread_function?is?running,argument?is?Hello?World!?? Message?is?now?marked?by?thread?? thread_result?is?Thank?you?for?the?cpu?time?? fs@ubuntu:~/qiang/thread/0107$???
從這個程序,我們可以看到線程之間是如何通信的,線程之間通過二級指針來傳送參數的地址(這是進程所不具備的,因為他們的地址空間獨立),但兩個線程之間的通信,傳遞的數據的生命周期必須是靜態的。可以使全局變量、static修飾的數據、堆里面的數據;這個程序中的message就是一個全局變量。其中一個線程可以修改它,另一個線程得到它修改過后的message。
?
二、線程的同步和互斥
先來了解同步和互斥的基本概念:
臨界資源:某些資源來說,其在同一時間只能被一段機器指令序列所占用。這些一次只能被一段指令序列所占用的資源就是所謂的臨界資源。
臨界區:對于臨界資源的訪問,必須是互斥進行。也就是當臨界資源被一個指令序列占用時,另一個需要訪問相同臨界資源的指令序列就不能被執行。指令序列不能執行的實際意思就是其所在的進程/線程會被阻塞。所以我們定義程序內訪問臨界資源的代碼序列被稱為臨界區。
互斥:是指同事只允許一個訪問者對臨界資源進行訪問,具有唯一性和排它性。但互斥無法限制訪問這個對資源的訪問順序,即訪問時無序的。
同步:是指在互斥的基礎上,通過其他機制實現訪問者對資源的有序訪問。
?
1、線程間互斥
引入互斥(mutual?? exlusion)鎖的目的是用來保證共享數據的完整性。
互斥鎖主要用來保護臨界資源。每個臨界資源都有一個互斥鎖來保護,任何時刻最多只能有一個線程能訪問該資源;線程必須先獲得互斥鎖才能訪問臨界資源,訪問完資源后釋放該鎖。如果無法獲得鎖,線程會阻塞直到獲得鎖為止;
通常,我們在臨界區前上鎖,臨界區后解鎖;
1)初始化互斥鎖函數
| 所需頭文件 | #include <pthread.h> |
| 函數原型 | int pthread_mutex_init (pthread_mutex_t? *mutex,? pthread_mutexattr_t? *attr ) //初始化互斥鎖 |
| 函數參數 | mutex:互斥鎖 attr :互斥鎖屬性 // NULL表示缺省屬性 |
| 函數返回值 | 成功:0 出錯:-1 |
2)申請互斥鎖函數
| 所需頭文件 | #include <pthread.h> |
| 函數原型 | int pthread_mutex_lock(pthread_mutex_t *mutex) //申請互斥鎖 |
| 函數參數 | mutex:互斥鎖 |
| 函數返回值 | 成功:0 出錯:-1 |
3)釋放互斥鎖函數
| 所需頭文件 | #include <pthread.h> |
| 函數原型 | int pthread_mutex_unlock(pthread_mutex_t *mutex) //釋放互斥鎖 |
| 函數參數 | mutex:互斥鎖 |
| 函數返回值 | 成功:0 出錯:-1 |
下面是一個實例:
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stdlib.h>?? #include?<string.h>?? #include?<pthread.h>?? #include?<unistd.h>?? ?? ?? unsigned?int?value1,value2,count;?? pthread_mutex_t?mutex;?? void?*function(void?*arg);?? ?? int?main()?? {?? ????pthread_t?a_thread;?? ?? ????if(pthread_mutex_init(&mutex,NULL)?<?0)?? ????{?? ????????perror("fail?to?mutex_init");?? ????????exit(-1);?? ????}?? ?? ????if(pthread_create(&a_thread,NULL,function,NULL)?!=?0)?? ????{?? ????????perror("fail?to?pthread_create");?? ????????exit(-1);?? ????}?? ?? ????while(1)?? ????{?? ????????count++;?? #ifdef?_LOCK_?? ????????pthread_mutex_lock(&mutex);?? #endif?? ????????value1?=?count;?? ????????value2?=?count;?? #ifdef?_LOCK_?? ????????pthread_mutex_unlock(&mutex);?? #endif?? ????}?? ????return?0;?? }?? ?? void?*function(void?*arg)?? {?? ????while(1)?? ????{?? #ifdef?_LOCK_?? ????????pthread_mutex_lock(&mutex);?? #endif?? ????????if(value1?!=?value2)?? ????????{?? ????????????printf("count?=?%d,value1?=?%d,value2?=?%d\n",count,value1,value2);?? ????????????usleep(100000);?? ????????}?? #ifdef?_LOCK_?? ????????pthread_mutex_unlock(&mutex);?? #endif?? ????}?? ????return?NULL;?? }??
執行結果如下:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/thread/0107$?./mutex?? ?? count?=?3368408,value1?=?3368408,value2?=?3368407?? count?=?44174760,value1?=?44174760,value2?=?44174759?? count?=?69313865,value1?=?69313865,value2?=?69313864?? count?=?139035309,value1?=?139035309,value2?=?139035308?? count?=?168803956,value1?=?168803956,value2?=?168803955?? count?=?192992611,value1?=?192992611,value2?=?192992610?? count?=?224279903,value1?=?224279903,value2?=?224279902?? count?=?259586793,value1?=?259586793,value2?=?259586792?? count?=?282057307,value1?=?282057307,value2?=?282057306?? count?=?321607823,value1?=?321607823,value2?=?321607822?? count?=?351629940,value1?=?351629940,value2?=?351629939?? count?=?374130545,value1?=?374130545,value2?=?374130544?? count?=?400727525,value1?=?400727525,value2?=?400727524?? count?=?440219988,value1?=?440219988,value2?=?440219987?? count?=?466069865,value1?=?466069865,value2?=?466069864?? count?=?500581241,value1?=?500581241,value2?=?500581240?? count?=?522649671,value1?=?522649671,value2?=?522649670?? count?=?569234325,value1?=?569234325,value2?=?569234324?? count?=?608139152,value1?=?608139152,value2?=?608139151?? count?=?639493957,value1?=?639493957,value2?=?639493956?? .....??
我們可以看到,數據是不斷被打印的,說明 a 線程是可以訪問臨界資源的。
我們把#define? _LOCK_前面的注釋去掉,這時就加上了互斥鎖,執行結果如下:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/thread/0107$?./mutex??
此時,并沒有數據被打印,說明此時a線程中 value1 與 value 2 一直是相等的,說明主線程執行是,a線程并無法訪問臨界資源的。
2、線程間同步
同步(synchronization)?指的是多個任務(線程)按照約定的順序相互配合完成一件事情;
線程間同步——P / V 操作
信號量代表某一類資源,其值表示系統中該資源當前可用的數量。
信號量是一個受保護的變量,只能通過三種操作來訪問:
1)初始化
2)P操作(申請資源)
3)V操作(釋放資源)P(S)含義如下:
[cpp]?view plaincopy
if?(信號量的值大于0)?? {?? ????請資源的任務繼續運行;?? ????信號量的值?減一;?? }?? else?? {?? ????請資源的任務阻塞;?? }??
V(S)含義如下:
[cpp]?view plaincopy
if?(沒有任務在等待該資源)?? {?? ????信號量的值?加一;?? }?? else?? {?? ????喚醒第一個等待的任務,讓其繼續運行;?? }??
1)、信號量初始化函數:
| 所需頭文件 | #include <semaphore.h> |
| 函數原型 | int sem_int (sem_t *sem,int pshared,unsigned int value) //初始化信號量 |
| 函數參數 | sem:初始化的信號量 pshared:信號量共享的范圍(0:線程間使用 非0 :進程間使用) value :信號量初值 |
| 函數返回值 | 成功:0 出錯:-1 |
2)P操作
| 所需頭文件 | #include <semaphore.h> |
| 函數原型 | int sem_wait (sem_t *sem) //P操作 |
| 函數參數 | sem:信號量 |
| 函數返回值 | 成功:0 出錯:-1 |
3)V操作
| 所需頭文件 | #include <semaphore.h> |
| 函數原型 | int sem_post(sem_t *sem) //V操作 |
| 函數參數 | sem:信號量 |
| 函數返回值 | 成功:0 出錯:-1 |
下面是個實例:
[cpp]?view plaincopy
#include?<stdio.h>?? #include?<stdlib.h>?? #include?<string.h>?? #include?<pthread.h>?? #include?<semaphore.h>?? ?? char?buf[60];?? sem_t?sem;?? void?*function(void?*arg);?? ?? int?main(int?argc,?char?*argv[])?? {?? ????pthread_t?a_thread;?? ????void?*thread_result;?? ?? ????if(sem_init(&sem,0,0)?!=?0)?? ????{?? ????????perror("fail?to?sem_init");?? ????????exit(-1);?? ????}?? ?? ????if(pthread_create(&a_thread,NULL,function,NULL)?!=?0)?? ????{?? ????????perror("fail?to?pthread_create");?? ????????exit(-1);?? ????}?? ?? ????printf("input?'quit'?to?exit\n");?? ????do???? ????{?? ????????fgets(buf,60,stdin);?? ????????sem_post(&sem);?? ????}?? ????while(strncmp(buf,"quit",4)?!=?0);?? ?????? ????return?0;?? }?? ?? void?*function(void?*arg)?? {?? ????while(1)?? ????{?? ????????sem_wait(&sem);?? ????????printf("you?enter?%d?characters\n",strlen(buf)?-?1);?? ????}?? }??
執行結果如下:
[cpp]?view plaincopy
fs@ubuntu:~/qiang/thread/0107$?./sem??? input?'quit'?to?exit?? xiao?? you?enter?4?characters?? zhi?? you?enter?3?characters?? qiang?? you?enter?5?characters?? quit?? fs@ubuntu:~/qiang/thread/0107$???
我們可以看到兩個線程是同步的。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎
總結
以上是生活随笔為你收集整理的Linux 系统应用编程——线程基础的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。