Linux-應用編程-學習筆記(19):線程全解

Linux-應用編程-學習筆記(19):線程全解

前言:線程是解決進程間通信的一個非常好的方法,它保留了進程技術實現多任務的特性,是內核調度的最小單元。多線程在多核心CPU上面更有優勢。

一、線程引入

1. 用線程來解決進程的劣勢

我們知道進程技術發明的主要目的是用來實現多任務系統需求(多任務的需求是客觀的)。
進程技術的主要功能是爲了實現CPU的分時複用,從而實現宏觀上的並行。但是要知道進程之間的調度(切換)是需要耗費一定成本的。比如A進程執行到某一步去切換B進程,首先需要保存A進程此時的斷點,然後去執行B進程,等到回來再恢復斷點繼續執行……這其中斷點得保存與恢復都是要付出代價的。並且進程間的通信由於進程隔離的緣故,會比較麻煩。
因此我們可以通過線程的方式來實現進程這些劣勢的彌補,線程技術不僅保留了進程技術實現多任務的特性,還在線程間切換和線程間通信上提升了效率,並且在多核心CPU上面更有優勢

2. 線程原理簡介

線程是參與內核調度的最小單元,一個進程中可以有多個線程(一個線程相當於一個分支任務),線程是依附於進程存在的(先得有了一個進程,纔能有裏面的線程),操作系統在調度時候調度的是進程裏面的線程
在這裏插入圖片描述
線程可以像進程一樣可被OS調度,繼承了進程能夠可以被單獨調用的特性。
同一進程的多個線程之間很容易高效率通信(同一個進程中的多個線程就好像同一個文件中的多個函數一樣),進程中線程與線程的通信就相當於函數與主函數之間的通信,可以使用全局變量即可。
在多核心CPU(對稱多處理器架構SMP)架構下效率最大化。可以保證多線程裏面的多個線程運行在不同的CPU核心上,實現一種並行同時運行的狀態。

3. 線程常見函數

線程的創建與回收:
(1)pthread_create :主線程用來創造子線程的
(2)pthread_join:主線程用來等待(阻塞)回收子線程
(3)pthread_detach: 主線程用來分離子線程,分離後主線程不必再去回收子線程(子線程自己管自己)

線程的取消:
(1)pthread_cancel: 一般都是主線程調用該函數去取消(讓它趕緊死)子線程
(2)pthread_setcancelstate :子線程設置自己是否允許被取消(如果設置宏爲不可被取消,那麼cancel不管用)
(3)pthread_setcanceltype:取消的時候的模式(允許取消時候纔會生效)

線程函數的退出:
(1)pthread_exit:標準的子線程退出方式(子線程不能調用exit返回,因爲你屬於進程的一部分,調用exit直接把進程整個都退出了)
(2)pthread_cleanup_push:解決破壞鎖的問題(具體分析見下面)
(3)pthread_cleanup_pop:接應push函數,將內部保存的信息從棧中取出來(0表示拿出來不執行,1表示拿出來執行)
在這裏插入圖片描述
解釋一下上述過程:
首先如果線程正常運行沒有被取消,那麼cnt在進行線程操作之前被加1(上鎖),線程運行結束後cnt減1(解鎖),其中cnt的變化爲0->1->0,解鎖後的cnt可以供其他線程使用。
如果線程在沒有結束時就被取消了,那麼cnt沒有經歷減1(解鎖),則其他線程將無法進入(鎖壞掉)。所以我們可以使用pthread_cleanup_push函數,在線程執行前先將解鎖函數(function)提前保存在棧中,如果線程被取消,那麼能夠通過棧中的解鎖函數來保證鎖不會壞掉。

二、線程同步方法

通過一個任務來實現線程間的同步。
任務:用戶從終端輸入任意字符然後統計個數顯示,輸入end則結束。

1. 使用信號量進行同步

線程同步就是主線程和子線程之間的配合。想要的效果是平時子線程時阻塞住的狀態,主線程獲取用戶輸入的字符,如果不等於“end”,那麼就去激活子線程,將輸入的內容打印出來。
使用信號量的方式就是通過定義一個cnt,使其爲0,在線程執行前給cnt++(上鎖),線程結束後給cnt–(解鎖)。這裏使用封裝好的庫函數即可。

sem_init:初始化未命名的信號量。
sem_post:發送解鎖信號給指向的信號量。
sem_wait:鎖定指向的信號量。
sem_destroy:銷燬定義的信號量。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

char buf[200] = {0};
//定義一個sem_t類型的變量
sem_t sem;
unsigned int flag = 0;

// 子線程程序,作用是統計buf中的字符個數並打印
void *func(void *arg)
{
	// 子線程首先應該有個循環
	// 循環中阻塞在等待主線程激活的時候,子線程被激活後就去獲取buf中的字符長度,然後打印;完成後再次被阻塞
	//阻塞住,等到發送信號才執行
	sem_wait(&sem);
	while (flag == 0)
	{	
		printf("本次輸入了%d個字符\n", strlen(buf));
		memset(buf, 0, sizeof(buf));
		sem_wait(&sem);
	}
	//退出線程
	pthread_exit(NULL);
}

int main(void)
{
	int ret = -1;
	pthread_t th = -1;
	//初始化一個信號量
	sem_init(&sem, 0, 0);
	//創建一個線程
	ret = pthread_create(&th, NULL, func, NULL);
	if (ret != 0)
	{
		printf("pthread_create error.\n");
		exit(-1);
	}
	
	printf("輸入一個字符串,以回車結束\n");
	while (scanf("%s", buf))	//如果有輸入內容進入循環
	{
		// 去比較用戶輸入的是不是end,如果是則退出,如果不是則繼續		
		if (!strncmp(buf, "end", 3))
		{
			printf("程序結束\n");
			flag = 1;
			//發送信號
			sem_post(&sem);	
			break;
		}
		// 主線程在收到用戶收入的字符串,並且確認不是end後就去發信號激活子線程來計數。
		// 子線程被阻塞,主線程可以激活,這就是線程的同步問題。
		// 信號量就可以用來實現這個線程同步
		sem_post(&sem);	
	}

	// 回收子線程
	printf("等待回收子線程\n");
	ret = pthread_join(th, NULL);
	if (ret != 0)
	{
		printf("pthread_join error.\n");
		exit(-1);
	}
	printf("子線程回收成功\n");
	//銷燬信號量
	sem_destroy(&sem);
	
	return 0;
}

在這裏插入圖片描述

2. 使用互斥鎖進行同步

互斥鎖又稱互斥量。互斥鎖和信號量的關係:可以認爲互斥鎖是一種特殊的信號量。信號量可以一直加(能夠實現排隊),而互斥鎖只能是0或者1。
互斥鎖一般用來實現關鍵段的保護,流程一般爲上鎖->執行->解鎖。

pthread_mutex_init:使用attr指定的屬性初始化由互斥鎖引用的互斥鎖。
pthread_mutex_lock:用來鎖定互斥鎖。
pthread_mutex_unlock:解除互斥鎖。
pthread_mutex_destroy:銷燬互斥鎖。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>

char buf[200] = {0};
//定義一個互斥鎖的變量
pthread_mutex_t mutex;
unsigned int flag = 0;

// 子線程程序,作用是統計buf中的字符個數並打印
void *func(void *arg)
{
	// 子線程首先應該有個循環
	// 循環中阻塞在等待主線程激活的時候,子線程被激活後就去獲取buf中的字符
	// 長度,然後打印;完成後再次被阻塞
	
	sleep(1);
	while (flag == 0)
	{	
		pthread_mutex_lock(&mutex);
		printf("本次輸入了%d個字符\n", strlen(buf));
		memset(buf, 0, sizeof(buf));
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}	
	pthread_exit(NULL);
}


int main(void)
{
	int ret = -1;
	pthread_t th = -1;
	//創建一個互斥鎖
	pthread_mutex_init(&mutex, NULL);
	//創建一個子線程
	ret = pthread_create(&th, NULL, func, NULL);
	if (ret != 0)
	{
		printf("pthread_create error.\n");
		exit(-1);
	}
	
	printf("輸入一個字符串,以回車結束\n");
	while (1)
	{
		//互斥鎖上鎖
		pthread_mutex_lock(&mutex);
		scanf("%s", buf);
		//互斥鎖解鎖
		pthread_mutex_unlock(&mutex);
		// 去比較用戶輸入的是不是end,如果是則退出,如果不是則繼續		
		if (!strncmp(buf, "end", 3))
		{
			printf("程序結束\n");
			flag = 1;

			break;
		}
		//這裏sleep的作用是確保主子線程不會搶鎖
		sleep(1);
	}

	
	// 回收子線程
	printf("等待回收子線程\n");
	ret = pthread_join(th, NULL);
	if (ret != 0)
	{
		printf("pthread_join error.\n");
		exit(-1);
	}
	printf("子線程回收成功\n");
	
	pthread_mutex_destroy(&mutex);
	
	return 0;
}

這裏只是一個簡單的互斥鎖使用示例,正常情況下互斥鎖用來保護一些關鍵的位置,這些位置有着明顯的先後順序,所以不至於用sleep來控制搶鎖。

3. 使用條件變量進行同步

條件變量是線程同步的一種特有的方法,也是效率比較高的一種方法。當線程不滿足條件時,就一直等待這個條件(阻塞),另一個提供條件的線程當滿足發送條件時,就會給這個線程發送一個條件從而激活這個阻塞住的線程,被激活之後就會繼續運行。
條件變量在使用時可以與互斥鎖配合使用

pthread_cond_init:初始化cond所引用的一個條件變量。
pthread_cond_destroy:銷燬被引用的條件變量。
pthread_cond_wait:該函數應在條件變量上阻塞。 它們應使用被調用線程鎖定的互斥鎖或未定義的行爲結果來調用。
pthread_cond_signal/pthread_cond_broadcast:這些函數應解除阻塞在條件變量上阻塞的線程。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>

char buf[200] = {0};
//定義互斥鎖變量
pthread_mutex_t mutex;
//定義條件變量
pthread_cond_t cond;
unsigned int flag = 0;

// 子線程程序,作用是統計buf中的字符個數並打印
void *func(void *arg)
{
	// 子線程首先應該有個循環
	// 循環中阻塞在等待主線程激活的時候,子線程被激活後就去獲取buf中的字符
	// 長度,然後打印;完成後再次被阻塞
	
	while (flag == 0)
	{	
		//上鎖
		pthread_mutex_lock(&mutex);
		//等待條件變量滿足才能繼續執行
		pthread_cond_wait(&cond, &mutex);
		printf("本次輸入了%d個字符\n", strlen(buf));
		memset(buf, 0, sizeof(buf));
		//解鎖
		pthread_mutex_unlock(&mutex);
	}
	//退出子線程
	pthread_exit(NULL);
}

int main(void)
{
	int ret = -1;
	pthread_t th = -1;
	//初始化一個互斥鎖
	pthread_mutex_init(&mutex, NULL);
	//初始化一個條件變量
	pthread_cond_init(&cond, NULL);
	//創建一個子線程
	ret = pthread_create(&th, NULL, func, NULL);
	if (ret != 0)
	{
		printf("pthread_create error.\n");
		exit(-1);
	}
	
	printf("輸入一個字符串,以回車結束\n");
	while (1)
	{
		scanf("%s", buf);
		//發送條件變量滿足信號
		pthread_cond_signal(&cond);
		// 去比較用戶輸入的是不是end,如果是則退出,如果不是則繼續		
		if (!strncmp(buf, "end", 3))
		{
			printf("程序結束\n");
			flag = 1;
			
			break;
		}
		// 主線程在收到用戶收入的字符串,並且確認不是end後
		// 就去發信號激活子線程來計數。
		// 子線程被阻塞,主線程可以激活,這就是線程的同步問題。
		// 信號量就可以用來實現這個線程同步
	}
	// 回收子線程
	printf("等待回收子線程\n");
	ret = pthread_join(th, NULL);
	if (ret != 0)
	{
		printf("pthread_join error.\n");
		exit(-1);
	}
	printf("子線程回收成功\n");
	//銷燬互斥鎖
	pthread_mutex_destroy(&mutex);
	//銷燬條件變量
	pthread_cond_destroy(&cond);
	return 0;
}

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章