色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux應用開發【第五章】線程編程應用開發

weidongshan ? 來源:weidongshan ? 作者:weidongshan ? 2021-12-10 19:15 ? 次閱讀

文章目錄

5 線程編程應用開發

5.1 線程的使用

5.1.1 為什么要使用多線程

5.1.2 線程概念

5.1.3 線程的標識pthread_t

5.1.4 線程的創建

5.1.5 向線程傳入參數

5.1.6 線程的退出與回收

5.2 線程的控制

5.2.1 多線程編臨界資源訪問

5.2.2 互斥鎖API簡述

5.2.3 多線程編執行順序控制

5.2.4 信號量API簡述

5.3 總結

5 線程編程應用開發

? 本章將分為兩大部分進行講解,前半部分將引出線程的使用場景及基本概念,通過示例代碼來說明一個線程創建到退出到回收的基本流程。后半部分則會通過示例代碼來說明如果控制好線程,從臨界資源訪問與線程的執行順序控制上引出互斥鎖、信號量的概念與使用方法。

5.1 線程的使用

5.1.1 為什么要使用多線程

? 在編寫代碼時,是否會遇到以下的場景會感覺到難以下手?

場景一:寫程序在拷貝文件時,需要一邊去拷貝文件,一邊去向用戶展示拷貝文件的進度時,傳統做法是通過每次拷貝完成結束后去更新變量,再將變量轉化為進度顯示出來。其中經歷了拷貝->計算->顯示->拷貝->計算->顯示…直至拷貝結束。這樣的程序架構及其的低效,必須在單次拷貝結束后才可以刷新當前拷貝進度,若可以將進程分支,一支單獨的解決拷貝問題,一支單獨的解決計算刷新問題,則程序效率會提升很多。

場景二:用阻塞方式去讀取數據,實時需要發送數據的時候。例如在進行串口數據傳輸或者網絡數據傳輸的時候,我們往往需要雙向通信,當設置讀取數據為阻塞模式時候,傳統的單線程只能等到數據接收來臨后才能沖過阻塞,再根據邏輯進行發送。當我們要實現隨時發送、隨時接收時,無法滿足我們的業務需求。若可以將進程分支,一支單純的處理接收數據邏輯,一支單純的解決發送數據邏輯,就可以完美的實現功能。

? 基于以上場景描述,多線程編程可以完美的解決上述問題。

5.1.2 線程概念

? 所謂線程,就是操作系統所能調度的最小單位。普通的進程,只有一個線程在執行對應的邏輯。我們可以通過多線程編程,使一個進程可以去執行多個不同的任務。相比多進程編程而言,線程享有共享資源,即在進程中出現的全局變量,每個線程都可以去訪問它,與進程共享“4G”內存空間,使得系統資源消耗減少。本章節來討論Linux下POSIX線程。

5.1.3 線程的標識pthread_t

? 對于進程而言,每一個進程都有一個唯一對應的PID號來表示該進程,而對于線程而言,也有一個“類似于進程的PID號”,名為tid,其本質是一個pthread_t類型的變量。線程號與進程號是表示線程和進程的唯一標識,但是對于線程號而言,其僅僅在其所屬的進程上下文中才有意義。

獲取線程號
#include 
pthread_t pthread_self(void);
成功:返回線程號

在程序中,可以通過函數pthread_self,來返回當前線程的線程號,例程1給出了打印線程tid號。

測試例程1:(Phtread_txex1.c)

1	#include 
2	#include 
3
4	int main()
5	{
6		pthread_t tid = pthread_self();//獲取主線程的tid號
7		printf("tid = %lun",(unsigned long)tid);
8       return 0;
9	}

注意:因采用POSIX線程接口,故在要編譯的時候包含pthread庫,使用gcc編譯應gcc xxx.c -lpthread 方可編譯多線程程序。

編譯結果:

pYYBAGGzNtuASgEkAABSG06ds1U232.png

5.1.4 線程的創建

創建線程
#include 
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
成功:返回0

? 在傳統的程序中,一個進程只有一個線程,可以通過函數pthread_create來創建線程。

? 該函數第一個參數為pthread_t類型的線程號地址,當函數執行成功后會指向新建線程的線程號;第二個參數表示了線程的屬性,一般傳入NULL表示默認屬性;第三個參數代表返回值為void *,形參為void *的函數指針,當線程創建成功后,會自動的執行該回調函數;第四個參數則表示為向線程處理函數傳入的參數,若不傳入,可用NULL填充,有關線程傳參后續小節會有詳細的說明,接下來通過一個簡單例程來使用該函數創建出一個線程。

測試例程2:(Phtread_txex2.c)

1 	#include 
2 	#include 
3 	#include 
4 	#include 
5 
6 	void *fun(void *arg)
7 	{
8 		printf("pthread_New = %lun",(unsigned long)pthread_self());//打印線程的tid號
9 	}
10
11	int main()
12	{
13		
14		pthread_t tid1;
15		int ret = pthread_create(&tid1,NULL,fun,NULL);//創建線程
16		if(ret != 0){
17			perror("pthread_create");
18			return -1;
19		}
20		
21		/*tid_main 為通過pthread_self獲取的線程ID,tid_new通過執行pthread_create成功后tid指向的空間*/
22		printf("tid_main = %lu tid_new = %lu n",(unsigned long)pthread_self(),(unsigned long)tid1);
23		
24		/*因線程執行順序隨機,不加sleep可能導致主線程先執行,導致進程結束,無法執行到子線程*/
25		sleep(1);
26		
27		return 0;
28	}
29

運行結果:

poYBAGGzNuCAcno0AABWODrBxe8904.png

? 通過pthread_create確實可以創建出來線程,主線程中執行pthread_create后的tid指向了線程號空間,與子線程通過函數pthread_self打印出來的線程號一致。

? 特別說明的是,當主線程伴隨進程結束時,所創建出來的線程也會立即結束,不會繼續執行。并且創建出來的線程的執行順序是隨機競爭的,并不能保證哪一個線程會先運行??梢詫⑸鲜龃a中sleep函數進行注釋,觀察實驗現象。

去掉上述代碼25行后運行結果:

pYYBAGGzNuaAflziAACuPprQWJU472.png

? 上述運行代碼3次,其中有2次被進程結束,無法執行到子線程的邏輯,最后一次則執行到了子線程邏輯后結束的進程。如此可以說明,線程的執行順序不受控制,且整個進程結束后所產生的線程也隨之被釋放,在后續內容中將會描述如何控制線程執行。

5.1.5 向線程傳入參數

? pthread_create()的最后一個參數的為void *類型的數據,表示可以向線程傳遞一個void *數據類型的參數,線程的回調函數中可以獲取該參數,例程3舉例了如何向線程傳入變量地址與變量值。

測試例程3:(Phtread_txex3.c)

1 	#include 
2 	#include 
3 	#include 
4 	#include 
5 
6 	void *fun1(void *arg)
7 	{
8 		printf("%s:arg = %d Addr = %pn",__FUNCTION__,*(int *)arg,arg);
9 	}
10
11	void *fun2(void *arg)
12	{
13		printf("%s:arg = %d Addr = %pn",__FUNCTION__,(int)(long)arg,arg);
14	}
15
16	int main()
17	{
18
19		pthread_t tid1,tid2;
20		int a = 50;
21		int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);//創建線程傳入變量a的地址
22		if(ret != 0){
23			perror("pthread_create");
24			return -1;
25		}
27		ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);//創建線程傳入變量a的值
28		if(ret != 0){
29			perror("pthread_create");
30			return -1;
31		}
32		sleep(1);
33		printf("%s:a = %d Add = %p n",__FUNCTION__,a,&a);
34		return 0;
35	}
36

運行結果:

poYBAGGzNueAQ4hbAABFf_JAsok336.png

? 本例程展示了如何利用線程創建函數的第四個參數向線程傳入數據,舉例了如何以地址的方式傳入值、以變量的方式傳入值,例程代碼的21行,是將變量a先行取地址后,再次強制類型轉化為void后傳入線程,線程處理的回調函數中,先將萬能指針void *轉化為int *,再次取地址就可以獲得該地址變量的值,其本質在于地址的傳遞。例程代碼的27行,直接將int類型的變量強制轉化為void *進行傳遞(針對不同位數機器,指針對其字數不同,需要int轉化為long在轉指針,否則可能會發生警告),在線程處理回調函數中,直接將void *數據轉化為int類型即可,本質上是在傳遞變量a的值。

? 上述兩種方法均可得到所要的值,但是要注意其本質,一個為地址傳遞,一個為值的傳遞。當變量發生改變時候,傳遞地址后,該地址所對應的變量也會發生改變,但傳入變量值的時候,即使地址指針所指的變量發生變化,但傳入的為變量值,不會受到指針的指向的影響,實際項目中切記兩者之間的區別。具體說明見例程4.

測試例程4:(Phtread_txex4.c)

1 	#include 
2 	#include 
3 	#include 
4 	#include 
5 
6 	void *fun1(void *arg)
7 	{
8 		while(1){
9 		
10			printf("%s:arg = %d Addr = %pn",__FUNCTION__,*(int *)arg,arg);
11			sleep(1);
12		}
13	}
14
15	void *fun2(void *arg)
16	{
17		while(1){
18		
19			printf("%s:arg = %d Addr = %pn",__FUNCTION__,(int)(long)arg,arg);
20			sleep(1);
21		}
22	}
23
24	int main()
25	{
26
27		pthread_t tid1,tid2;
28		int a = 50;
29		int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
30		if(ret != 0){
31			perror("pthread_create");
32			return -1;
33		}
34		sleep(1);
35		ret = pthread_create(&tid2,NULL,fun2,(void *)(long)a);
36		if(ret != 0){
37			perror("pthread_create");
38			return -1;
39		}
40		while(1){
41			a++;
42			sleep(1);
43			printf("%s:a = %d Add = %p n",__FUNCTION__,a,&a);
44		}
45		return 0;
46	}
47

運行結果:

pYYBAGGzNueAWpBUAAC0KbZ7H-g972.png

? 上述例程講述了如何向線程傳遞一個參數,在處理實際項目中,往往會遇到傳遞多個參數的問題,我們可以通過結構體來進行傳遞,解決此問題。

測試例程5:(Phtread_txex5.c)

1 	#include 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 
7 	struct Stu{
8 		int Id;
9 		char Name[32];
10		float Mark;
11	};
12
13	void *fun1(void *arg)
14	{
15		struct Stu *tmp = (struct Stu *)arg;
16		printf("%s:Id = %d Name = %s Mark = %.2fn",__FUNCTION__,tmp->Id,tmp->Name,tmp->Mark);
17		
18	}
19
20	int main()
21	{
22
23		pthread_t tid1,tid2;
24		struct Stu stu;
25		stu.Id = 10000;
26		strcpy(stu.Name,"ZhangSan");
27		stu.Mark = 94.6;
28
29		int ret = pthread_create(&tid1,NULL,fun1,(void *)&stu);
30		if(ret != 0){
31			perror("pthread_create");
32			return -1;
33		}
34		printf("%s:Id = %d Name = %s Mark = %.2fn",__FUNCTION__,stu.Id,stu.Name,stu.Mark);
35		sleep(1);
36		return 0;
37	}
38

運行結果:

poYBAGGzNuiARcs-AABQrn6lh3A813.png

5.1.6 線程的退出與回收

? 線程的退出情況有三種:第一種是進程結束,進程中所有的線程也會隨之結束。第二種是通過函數pthread_exit來主動的退出線程。第三種通過函數pthread_cancel被其他線程被動結束。當線程結束后,主線程可以通過函數pthread_join/pthread_tryjoin_np來回收線程的資源,并且獲得線程結束后需要返回的數據。

線程退出
#include 
void pthread_exit(void *retval);

? 該函數為線程退出函數,在退出時候可以傳遞一個void*類型的數據帶給主線程,若選擇不傳出數據,可將參數填充為NULL。

線程資源回收(阻塞)
#include 
int pthread_join(pthread_t thread, void **retval);
成功:返回0

? 該函數為線程回收函數,默認狀態為阻塞狀態,直到成功回收線程后被沖開阻塞。第一個參數為要回收線程的tid號,第二個參數為線程回收后接受線程傳出的數據。

線程資源回收(非阻塞)
#define _GNU_SOURCE            
#include 
int pthread_tryjoin_np(pthread_t thread, void **retval);
成功:返回0

? 該函數為非阻塞模式回收函數,通過返回值判斷是否回收掉線程,成功回收則返回0,其余參數與pthread_join一致。

線程退出(指定線程號)
#include 
int pthread_cancel(pthread_t thread);
成功:返回0

? 該函數傳入一個tid號,會強制退出該tid所指向的線程,若成功執行會返回0。

? 上述描述簡單的介紹了有關線程回收的API,下面通過例程來說明上述API。

測試例程6:(Phtread_txex6.c)

1 	#include 
2 	#include 
3 	#include 
4 	#include 
5 
6 	void *fun1(void *arg)
7 	{
8 		static int tmp = 0;//必須要static修飾,否則pthread_join無法獲取到正確值
9 		//int tmp = 0;
10		tmp = *(int *)arg;
11		tmp+=100;
12		printf("%s:Addr = %p tmp = %dn",__FUNCTION__,&tmp,tmp);
13		pthread_exit((void *)&tmp);//將變量tmp取地址轉化為void*類型傳出
14	}
15
16
17	int main()
18	{
19
20		pthread_t tid1;
21		int a = 50;
22		void *Tmp = NULL;//因pthread_join第二個參數為void**類型
23		int ret = pthread_create(&tid1,NULL,fun1,(void *)&a);
24		if(ret != 0){
25			perror("pthread_create");
26			return -1;
27		}
28		pthread_join(tid1,&Tmp);
29		printf("%s:Addr = %p Val = %dn",__FUNCTION__,Tmp,*(int *)Tmp);
30		return 0;
31	}
32

運行結果:

pYYBAGGzNuiAFnKmAAA-9c-8VVA605.png

? 上述例程先通過23行將變量以地址的形式傳入線程,在線程中做出了自加100的操作,當線程退出的時候通過線程傳參,用void*類型的數據通過pthread_join接受。此例程去掉了之前加入的sleep函數,原因是pthread_join函數具備阻塞的特性,直至成功收回掉線程后才會沖破阻塞,因此不需要靠考慮主線程會執行到30行結束進程的情況。特別要說明的是例程第8行,當變量從線程傳出的時候,需要加static修飾,對生命周期做出延續,否則無法傳出正確的變量值。

測試例程7:(Phtread_txex7.c)

1 	#define _GNU_SOURCE 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 
7 	void *fun(void *arg)
8 	{
9 		printf("Pthread:%d Come !n",(int )(long)arg+1);
10		pthread_exit(arg);
11	}
12
13
14	int main()
15	{
16		int ret,i,flag = 0;
17		void *Tmp = NULL;
18		pthread_t tid[3];
19		for(i = 0;i < 3;i++){
20			ret = pthread_create(&tid[i],NULL,fun,(void *)(long)i);
21			if(ret != 0){
22				perror("pthread_create");
23				return -1;
24			}
25		}
26		while(1){//通過非阻塞方式收回線程,每次成功回收一個線程變量自增,直至3個線程全數回收
27			for(i = 0;i <3;i++){
28				if(pthread_tryjoin_np(tid[i],&Tmp) == 0){
29					printf("Pthread : %d exit !n",(int )(long )Tmp+1);
30					flag++;	
31				}
32			}
33			if(flag >= 3) break;
34		}
35		return 0;
36	}
37

運行結果:

poYBAGGzNuiAF0lBAABDsVuBsGU093.png

? 例程7展示了如何使用非阻塞方式來回收線程,此外也展示了多個線程可以指向同一個回調函數的情況。例程6通過阻塞方式回收線程幾乎規定了線程回收的順序,若最先回收的線程未退出,則一直會被阻塞,導致后續先退出的線程無法及時的回收。

? 通過函數pthread_tryjoin_np,使用非阻塞回收,線程可以根據退出先后順序自由的進行資源的回收。

測試例程8:(Phtread_txex8.c)

1 	#define _GNU_SOURCE 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 
7 	void *fun1(void *arg)
8 	{
9 		printf("Pthread:1 come!n");
10		while(1){
11			sleep(1);
12		}
13	}
14
15	void *fun2(void *arg)
16	{
17		printf("Pthread:2 come!n");
18		pthread_cancel((pthread_t )(long)arg);//殺死線程1,使之強制退出
19		pthread_exit(NULL);
20	}
21
22	int main()
23	{
24		int ret,i,flag = 0;
25		void *Tmp = NULL;
26		pthread_t tid[2];
27		ret = pthread_create(&tid[0],NULL,fun1,NULL);
28		if(ret != 0){
29			perror("pthread_create");
30			return -1;
31		}
32		sleep(1);
33		ret = pthread_create(&tid[1],NULL,fun2,(void *)tid[0]);//傳輸線程1的線程號
34		if(ret != 0){
35			perror("pthread_create");
36			return -1;
37		}
38		while(1){//通過非阻塞方式收回線程,每次成功回收一個線程變量自增,直至2個線程全數回收

39			for(i = 0;i <2;i++){
40				if(pthread_tryjoin_np(tid[i],NULL) == 0){
41					printf("Pthread : %d exit !n",i+1);
42					flag++;	
43				}
44			}
45			if(flag >= 2) break;
46		}
47		return 0;
48	}
49

運行結果:

pYYBAGGzNumAXoOlAAA9GjK3bd4502.png

? 例程8展示了如何利用pthread_cancel函數主動的將某個線程結束。27行與33行創建了線程,將第一個線程的線程號傳參形式傳入了第二個線程。第一個的線程執行死循環睡眠邏輯,理論上除非進程結束,其永遠不會結束,但在第二個線程中調用了pthread_cancel函數,相當于向該線程發送一個退出的指令,導致線程被退出,最終資源被非阻塞回收掉。此例程要注意第32行的sleep函數,一定要確保線程1先執行,因線程是無序執行,故加入該睡眠函數控制順序,在本章后續,會講解通過加鎖、信號量等手段來合理的控制線程的臨界資源訪問與線程執行順序控制。

5.2 線程的控制

5.2.1 多線程編臨界資源訪問

? 當線程在運行過程中,去操作公共資源,如全局變量的時候,可能會發生彼此“矛盾”現象。例如線程1企圖想讓變量自增,而線程2企圖想要變量自減,兩個線程存在互相競爭的關系導致變量永遠處于一個“平衡狀態”,兩個線程互相競爭,線程1得到執行權后將變量自加,當線程2得到執行權后將變量自減,變量似乎永遠在某個范圍內浮動,無法到達期望數值,如例程9所示。

測試例程9:(Phtread_txex9.c)

1 	#define _GNU_SOURCE 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 
7 
8 	int Num = 0;
9 
10	void *fun1(void *arg)
11	{
12		while(Num < 3){
13			Num++;
14			printf("%s:Num = %dn",__FUNCTION__,Num);
15			sleep(1);
16		}
17		pthread_exit(NULL);
18	}
19
20	void *fun2(void *arg)
21	{
22		while(Num > -3){
23			Num--;
24			printf("%s:Num = %dn",__FUNCTION__,Num);
25			sleep(1);
26		}
27		pthread_exit(NULL);
28	}
29
30	int main()
31	{
32		int ret;
33		pthread_t tid1,tid2;
34		ret = pthread_create(&tid1,NULL,fun1,NULL);
35		if(ret != 0){
36			perror("pthread_create");
37			return -1;
38		}
39		ret = pthread_create(&tid2,NULL,fun2,NULL);
40		if(ret != 0){
41			perror("pthread_create");
42			return -1;
43		}
44		pthread_join(tid1,NULL);
45		pthread_join(tid2,NULL);
46		return 0;
47	}
48

運行結果:

poYBAGGzNumAGAk6AABNTevNl38286.png

? 為了解決上述對臨界資源的競爭問題,pthread線程引出了互斥鎖來解決臨界資源訪問。通過對臨界資源加鎖來保護資源只被單個線程操作,待操作結束后解鎖,其余線程才可獲得操作權。

5.2.2 互斥鎖API簡述

初始化互斥鎖
#include 
int pthread_mutex_init(phtread_mutex_t *mutex,
const pthread_mutexattr_t *restrict attr);
成功:返回0

? 該函數作用為初始化一個互斥鎖,一般情況申請一個全局的pthread_mutex_t類型的互斥鎖變量,通過此函數完成鎖內的初始化,第一個函數將該變量的地址傳入,第二個參數為控制互斥鎖的屬性,一般為NULL。當函數成功后會返回0,代表初始化互斥鎖成功。當然初始化互斥鎖也可以調用宏來快速初始化:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITALIZER;

互斥鎖加鎖(阻塞)/解鎖 
#include 
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

? lock函數與unlock函數分別為加鎖解鎖函數,只需要傳入已經初始化好的pthread_mutex_t互斥鎖變量,成功后會返回0。當某一個線程獲得了執行權后,執行lock函數,一旦加鎖成功后,其余線程遇到lock函數時候會發生阻塞,直至獲取資源的線程執行unlock函數后,獲得第二執行權的線程的阻塞模式被從開,同時也獲取了lock,導致其余線程同樣在阻塞,直至執行unlock被解鎖。

? 特別注意的是,當獲取lock之后,必須在邏輯處理結束后執行unlock,否則會發生死鎖現象!導致其余線程一直處于阻塞狀態,無法執行下去。在使用互斥鎖的時候,尤其要注意使用pthread_cancel函數,防止發生死鎖現象!

互斥鎖加鎖(非阻塞)
#include 
 int pthread_mutex_trylock(pthread_mutex_t *mutex);
成功:返回0

? 該函數同樣也是一個線程加鎖函數,但該函數是非阻塞模式通過返回值來判斷是否加鎖成功,用法與上述阻塞加速函數一致。

互斥鎖銷毀
#include 
int pthread_mutex_destory(pthread_mutex_t *mutex);
成功:返回0

? 該函數是用于銷毀互斥鎖的,傳入互斥鎖的地址,就可以完成互斥鎖的銷毀,成功返回0。

測試例程10:(Phtread_txex10.c)

1 	#define _GNU_SOURCE 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 
7 	pthread_mutex_t mutex;//互斥鎖變量 一般申請全局變量
8 
9 	int Num = 0;//公共臨界變量
10
11	void *fun1(void *arg)
12	{
13		pthread_mutex_lock(&mutex);//加鎖 若有線程獲得鎖,則會阻塞
14		while(Num < 3){
15			Num++;
16			printf("%s:Num = %dn",__FUNCTION__,Num);
17			sleep(1);
18		}
19		pthread_mutex_unlock(&mutex);//解鎖
20		pthread_exit(NULL);//線程退出 pthread_join會回收資源
21	}
22
23	void *fun2(void *arg)
24	{
25		pthread_mutex_lock(&mutex);//加鎖 若有線程獲得鎖,則會阻塞
26		while(Num > -3){
27			Num--;
28			printf("%s:Num = %dn",__FUNCTION__,Num);
29			sleep(1);
30		}
31		pthread_mutex_unlock(&mutex);//解鎖
32		pthread_exit(NULL);//線程退出 pthread_join會回收資源
33	}
34
35	int main()
36	{
37		int ret;
38		pthread_t tid1,tid2;
39		ret = pthread_mutex_init(&mutex,NULL);//初始化互斥鎖
40		if(ret != 0){
41			perror("pthread_mutex_init");
42			return -1;
43		}
44		ret = pthread_create(&tid1,NULL,fun1,NULL);//創建線程1
45		if(ret != 0){
46			perror("pthread_create");
47			return -1;
48		}
49		ret = pthread_create(&tid2,NULL,fun2,NULL);//創建線程2
50		if(ret != 0){
51			perror("pthread_create");
52			return -1;
53		}
54		pthread_join(tid1,NULL);//阻塞回收線程1
55		pthread_join(tid2,NULL);//阻塞回收線程2
56		pthread_mutex_destroy(&mutex);//銷毀互斥鎖
57		return 0;
58	}
59

運行結果:

pYYBAGGzNumAVRaMAABjrgJE-3M522.png

? 上述例程通過加入互斥鎖,保證了臨界變量某一時刻只被某一線程控制,實現了臨界資源的控制。需要說明的是,線程加鎖在循環內與循環外的情況。本歷程在進入while循環前進行了加鎖操作,在循環結束后進行的解鎖操作,如果將加鎖解鎖全部放入while循環內,作為單核的機器,執行結果無異,當有多核機器執行代碼時,可能會發生“搶鎖”現象,這取決于操作系統底層的實現。

5.2.3 多線程編執行順序控制

? 解決了臨界資源的訪問,但似乎對線程的執行順序無法得到控制,因線程都是無序執行,之前采用sleep強行延時的方法勉強可以控制執行順序,但此方法在實際項目情況往往是不可取的,其僅僅可解決線程創建的順序,當創建之后執行的順序又不會受到控制,于是便引入了信號量的概念,解決線程執行順序。

? 例程11將展示線程的執行的隨機性。

測試例程11:(Phtread_txex11.c)

1 	#define _GNU_SOURCE 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 
7 	void *fun1(void *arg)
8 	{
9 		printf("%s:Pthread Come!n",__FUNCTION__);
10		pthread_exit(NULL);
11	}
12
13	void *fun2(void *arg)
14	{
15		printf("%s:Pthread Come!n",__FUNCTION__);
16		pthread_exit(NULL);
17	}
18
19	void *fun3(void *arg)
20	{
21		printf("%s:Pthread Come!n",__FUNCTION__);
22		pthread_exit(NULL);
23	}
24
25	int main()
26	{
27		int ret;
28		pthread_t tid1,tid2,tid3;
29		ret = pthread_create(&tid1,NULL,fun1,NULL);
30		if(ret != 0){
31			perror("pthread_create");
32			return -1;
33		}
34		ret = pthread_create(&tid2,NULL,fun2,NULL);
35		if(ret != 0){
36			perror("pthread_create");
37			return -1;
38		}
39		ret = pthread_create(&tid3,NULL,fun3,NULL);
40		if(ret != 0){
41			perror("pthread_create");
42			return -1;
43		}
44		pthread_join(tid1,NULL);
45		pthread_join(tid2,NULL);
46		pthread_join(tid3,NULL);
47		return 0;
48	}
49

運行結果:

poYBAGGzNuqAVLGEAACxAMhPs_U987.png

? 通過上述例程可以發現,多次執行該函數其次序是無序的,線程之間的競爭無法控制,通過使用信號量來使得線程順序為可控的。

5.2.4 信號量API簡述

初始化信號量
#include 
int sem_init(sem_t *sem,int pshared,unsigned int value);
成功:返回0

? 該函數可以初始化一個信號量,第一個參數傳入sem_t類型的地址,第二個參數傳入0代表線程控制,否則為進程控制,第三個參數表示信號量的初始值,0代表阻塞,1代表運行。待初始化結束信號量后,若執行成功會返回0。

信號量PV操作(阻塞)
#include 
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
成功:返回0

? sem_wait函數作用為檢測指定信號量是否有資源可用,若無資源可用會阻塞等待,若有資源可用會自動的執行“sem-1”的操作。所謂的“sem-1”是與上述初始化函數中第三個參數值一致,成功執行會返回0.

? sem_post函數會釋放指定信號量的資源,執行“sem+1”操作。

? 通過以上2個函數可以完成所謂的PV操作,即信號量的申請與釋放,完成對線程執行順序的控制。

信號量申請資源(非阻塞)
#include 
int sem_trywait(sem_t *sem);
成功:返回0

? 與互斥鎖一樣,此函數是控制信號量申請資源的非阻塞函數,功能與sem_wait一致,唯一區別在于此函數為非阻塞。

信號量銷毀
#include 
int sem_destory(sem_t *sem);
成功:返回0

? 該函數為信號量銷毀函數,執行過后可將申請的信號量進行銷毀。

測試例程12:(Phtread_txex12.c)

1 	#define _GNU_SOURCE 
2 	#include 
3 	#include 
4 	#include 
5 	#include 
6 	#include 
7 
8 	sem_t sem1,sem2,sem3;//申請的三個信號量變量
9 
10	void *fun1(void *arg)
11	{
12		sem_wait(&sem1);//因sem1本身有資源,所以不被阻塞 獲取后sem1-1 下次會會阻塞
13		printf("%s:Pthread Come!n",__FUNCTION__);
14		sem_post(&sem2);// 使得sem2獲取到資源
15		pthread_exit(NULL);
16	}
17
18	void *fun2(void *arg)
19	{
20		sem_wait(&sem2);//因sem2在初始化時無資源會被阻塞,直至14行代碼執行 不被阻塞 sem2-1 下次會阻塞
21		printf("%s:Pthread Come!n",__FUNCTION__);
22		sem_post(&sem3);// 使得sem3獲取到資源
23		pthread_exit(NULL);
24	}
25
26	void *fun3(void *arg)
27	{
28		sem_wait(&sem3);//因sem3在初始化時無資源會被阻塞,直至22行代碼執行 不被阻塞 sem3-1 下次會阻塞
29		printf("%s:Pthread Come!n",__FUNCTION__);
30		sem_post(&sem1);// 使得sem1獲取到資源
31		pthread_exit(NULL);
32	}
33
34	int main()
35	{
36		int ret;
37		pthread_t tid1,tid2,tid3;
38		ret = sem_init(&sem1,0,1);  //初始化信號量1 并且賦予其資源
39		if(ret < 0){
40			perror("sem_init");
41			return -1;
42		}
43		ret = sem_init(&sem2,0,0); //初始化信號量2 讓其阻塞
44		if(ret < 0){
45			perror("sem_init");
46			return -1;
47		}
48		ret = sem_init(&sem3,0,0); //初始化信號3 讓其阻塞
49		if(ret < 0){
50			perror("sem_init");
51			return -1;
52		}
53		ret = pthread_create(&tid1,NULL,fun1,NULL);//創建線程1
54		if(ret != 0){
55			perror("pthread_create");
56			return -1;
57		}
58		ret = pthread_create(&tid2,NULL,fun2,NULL);//創建線程2
59		if(ret != 0){
60			perror("pthread_create");
61			return -1;
62		}
63		ret = pthread_create(&tid3,NULL,fun3,NULL);//創建線程3
64		if(ret != 0){
65			perror("pthread_create");
66			return -1;
67		}
68		/*回收線程資源*/
69		pthread_join(tid1,NULL);
70		pthread_join(tid2,NULL);
71		pthread_join(tid3,NULL);
72
73		/*銷毀信號量*/
74		sem_destroy(&sem1);
75		sem_destroy(&sem2);
76		sem_destroy(&sem3);
77
78		return 0;
79	}
80

運行結果:

pYYBAGGzNuqAQji-AACyy7gOv5E062.png

? 該例程加入了信號量的控制使得線程的執行順序變為可控的,在初始化信號量時,將信號量1填入資源,使之不被sem_wait函數阻塞,在執行完邏輯后使用sem_pos函數來填入即將要執行的資源。當執行函數sem_wait后,會執行sem自減操作,使下一次競爭被阻塞,直至通過sem_pos被釋放。

? 上述例程因38行初始化信號量1時候,使其默認獲取到資源,43、48行初始化信號量2、3時候,使之沒有資源。于是在線程處理函數中,每個線程通過sem_wait函數來等待資源,發送阻塞現象。因信號量1初始值為有資源,故可以先執行線程1的邏輯。待執行完第12行sem_wait函數,會導致sem1-1,使得下一次此線程會被阻塞。繼而執行至14行,通過sem_post函數使sem2信號量獲取資源,從而沖破阻塞執行線程2的邏輯…以此類推完成線程的有序控制。

5.3 總結

? 有關多線程的創建流程下圖所示,首先需要創建線程,一旦線程創建完成后,線程與線程之間會發生競爭執行,搶占時間片來執行線程邏輯。在創建線程時候,可以通過創建線程的第四個參數傳入參數,在線程退出時亦可傳出參數被線程回收函數所回收,獲取到傳出的參數。

poYBAGGzNuuAAt27AAC4Mt7xHBc147.png

線程編程流程

? 當多個線程出現后,會遇到同時操作臨界公共資源的問題,當線程操作公共資源時需要對線程進行保護加鎖,防止其與線程在此線程更改變量時同時更改變量,待邏輯執行完畢后再次解鎖,使其余線程再度開始競爭。互斥鎖創建流程下圖所示。

pYYBAGGzNuyAdDi8AAC562s_UuM124.png

互斥鎖編程流程

? 當多個線程出現后,同時會遇到無序執行的問題。有時候需要對線程的執行順序做出限定,變引入了信號量,通過PV操作來控制線程的執行順序,下圖所示。

poYBAGGzNuyABzr4AADj4aTbBLY326.png

信號量編程流程

審核編輯黃昊宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    87

    文章

    11342

    瀏覽量

    210140
  • 線程
    +關注

    關注

    0

    文章

    505

    瀏覽量

    19725
收藏 人收藏

    評論

    相關推薦

    Linux操作系統-C語言編程入門介紹在LINUX 下進行C 語言編程所需要的基礎知識.在這篇

    …………………………………………27第五章 信號處理 …………………………………………31第六 消息管理 ……………………………………………39第七 線程操作 ………………………
    發表于 12-08 09:56

    書籍教程:linux操作系統下c語言編程入門

    …………………………………………………………………………10第三 文件操作 …………………………………………………………………………17第四 時間概念 …………………………………………………………………………27第五章
    發表于 07-26 22:34

    《測控電路》習題完整參考答案(第五章

    《測控電路》習題完整參考答案(第五章
    發表于 05-07 11:34

    C語言編程入門(linux環境)

    消息管理 …………………………………………………………………………39第七 線程操作 …………………………………………………………………………49第八 網絡編程 ……………………
    發表于 12-19 13:57

    三相電路(第五章)

    三相電路(第五章) 目錄5.15.1三相電壓5.25.2負載星形聯結的三相電路5.35.3負載三角形聯結的三相電路
    發表于 04-14 11:11 ?26次下載

    集成運算放大器基礎 第五章 PPT

    集成運算放大器基礎 第五章 PPT
    發表于 04-20 09:38 ?178次下載
    集成運算放大器基礎 <b class='flag-5'>第五章</b> PPT

    高頻電子線路第五章答案

    高頻電子線路第五章答案.
    發表于 06-05 10:40 ?40次下載

    linux線程編程開發

    本文中我們針對 Linux 上多線程編程的主要特性總結出 5 條經驗,用以改善 Linux線程編程
    發表于 12-26 14:24 ?55次下載
    <b class='flag-5'>linux</b>多<b class='flag-5'>線程</b><b class='flag-5'>編程</b><b class='flag-5'>開發</b>

    《測控電路》習題完整參考答案(第五章

    《測控電路》習題完整參考答案(第五章
    發表于 02-07 15:17 ?0次下載

    數字信號處理 第五章

    數字信號處理 第五章
    發表于 10-19 09:33 ?4次下載
    數字信號處理 <b class='flag-5'>第五章</b>

    靜噪基礎第五章_導體傳導和共模

    靜噪基礎第五章,導體傳導和共模
    發表于 01-24 16:18 ?3次下載

    電力系統繼電保護第五章-自動重合閘(課件)下載.PPT

    繼電保護第五章-自動重合閘(課件)
    發表于 04-28 09:40 ?0次下載
    電力系統繼電保護<b class='flag-5'>第五章</b>-自動重合閘(課件)下載.PPT

    計算機網絡第五章運輸層課件下載

    計算機網絡第五章運輸層課件下載
    發表于 05-17 10:42 ?0次下載

    電路分析基礎第五章正弦電路的穩態分析課件下載

    電路分析基礎第五章正弦電路的穩態分析課件下載
    發表于 02-11 09:09 ?0次下載

    【北京迅為】《stm32mp157開發板嵌入式linux開發指南》第五章 Ubuntu使用apt-get下載

    【北京迅為】《stm32mp157開發板嵌入式linux開發指南》第五章 Ubuntu使用apt-get下載
    的頭像 發表于 09-03 16:26 ?842次閱讀
    【北京迅為】《stm32mp157<b class='flag-5'>開發</b>板嵌入式<b class='flag-5'>linux</b><b class='flag-5'>開發</b>指南》<b class='flag-5'>第五章</b> Ubuntu使用apt-get下載
    主站蜘蛛池模板: 日本一区二区三区在线观看网站 | 男女床上黄色 | 短篇合集纯肉高H深陷骚 | 伊人久久亚洲综合天堂 | 最近日本MV字幕免费观看在线 | 国拍在线精品视频免费观看 | 久久亚洲AV无码精品午色夜麻豆 | 亚洲一级毛片免费在线观看 | 国产精品久久久久久久久99热 | 爱看吧孕妇网 | 99久久久国产精品免费调教 | 最懂男人心论坛 | 微拍秒拍99福利精品小视频 | 国语自产二区高清国语自产拍 | 色欲无码国产喷水AV精品 | 妈妈的朋友6未删减版完整在线 | 国产精品高清m3u8在线播放 | 扒开她的黑森林让我添动态图 | 女人张腿让男人桶免费 | 在教室伦流澡到高潮H女攻视频 | 宝贝乖女好紧好深好爽老师 | 乐乐亚洲精品综合影院 | Y8848高清私人影院软件优势 | xxxx免费观看 | 奶大B紧17p| 69国产精品成人无码视频 | 午夜无码国产理论在线 | 草莓AV福利网站导航 | 野花日本免费完整版高清版动漫 | 欧美成人3d动漫专区 | 欧美美女论坛 | 内射少妇三洞齐开 | 大岛优香久久中文字幕 | 美女厕所撒尿ass | 国产精品网红女主播久久久 | 别插我B嗯啊视频免费 | 成 人 免费 黄 色 网站无毒下载 | 色99蜜臀AV无码 | 亚洲在线无码免费观看 | 亚洲日本天堂在线 | 日本一区二区三区在线观看网站 |