linux內核學習-線程-線程控制

線程:可以理解爲輕量級的CPU執行單元。其必須依託在進程內部。

同一個進程裏面的多個線程是共享進程的資源的。但是線程也有自己的私有數據,包括:

  • 線程號(thread ID)
  • 寄存器
  • 堆棧
  • 信號掩碼
  • 優先級
  • 線程私有存儲空間

線程的頭文件爲pthread.h 鏈接庫爲libpthread.a

一、創建線程

在這裏插入圖片描述
線程常用函數

函數 說明
pthread_t pthread_self(void) 獲取本線程的線程ID
int pthread_equal(pthread_t thread1,pthread_t thread2) 判斷兩個線程ID是否指向同一個線程
int pthread_once(pthread_once_t *once_control,void(*int_routine)(void)) 用來保證init_routine線程函數在進程中僅執行一次

測試程序

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

int* thread(void *arg)
{
	pthread_t newthid;
	
	newthid = pthread_self();
	printf("this is a new thread ,thread ID =%u\n",newthid);
	return NULL;
}

int main(void)
{
	pthread_t thid;
	
	printf("main thread ,ID is %u\n",pthread_self());//打印主線程的ID
	if(pthread_create(&thid,NULL, (void *)thread,NULL)!=0)
	{
		printf("thread creation failed \n");
		exit(1);
	}
	sleep(1);
	exit(0);
}

gcc在編譯的時候,不指定庫,會報錯
在這裏插入圖片描述
原因
由於pthread庫不是Linux系統默認的庫,連接時需要使用庫libpthread.a,所以在使用pthread_create創建線程時,在編譯中要加-lpthread參數:
gcc -o pthread -lpthread pthread.c

一個有趣的現象
我在程序中死循環創建線程,且線程不退出,發現,CPU的使用率確實很高,但是整個系統並沒有卡死的現象。

二、線程屬性

參數pthread_attr_t結構體的具體含義
在這裏插入圖片描述
datachstate:表示創建的線程是否與進程中的其他線程脫離同步。
schedpolicy:新線程調度策略
schedparam:調度涉及到的一個結構體(sched_param)
inheritsched:調度是否用繼承的。
scop:線程間的CPU競爭範圍
guardsize:警戒堆棧大小
stackaddr_set:堆棧地址集
stacksize:堆棧的大小

在這裏插入圖片描述

三、線程終止

使用pthread_exit()
有兩種情況會導致線程退出:1.main函數return或exit了。2.主線程調用了pthread_exit。
線程終止,需要關注兩個大問題:
1.臨界資源(就是資源在一個時間內只有一個線程能用,想用的線程只能等待。)。在終止前,需要做好釋放工作。具體如下:
linux系統提供了一對函數:pthread_cleanup_push()和pthread_cleanup_pop()用於自動釋放資源。在使用時,這兩個函數必須成對使用,且在同一段代碼內部。(原因是這兩個函數的本質其實是一段宏代碼)。
2.線程同步問題。線程中也有類似進程的wait函數,其作用就是等待某一個線程結束了自己才結束。對應的函數是pthread_join()。需要注意

  • 被等待的線程不能處於DETACHED狀態(既調用pthread_detach函數)。
  • 一個線程不能被多個線程等待。否則出了第一個會正常收到返回,其他都會返回錯誤代碼ESRCH。

示例代碼:

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

void assisthread(void *arg)
{
	printf("I am helping to do some jobs\n");
	sleep(3);
	pthread_exit(0);
}

int main(void)
{
	pthread_t assistthid;
	int status;
	
	pthread_create(&assistthid,NULL ,(void *)assisthread,NULL);
	pthread_join(assistthid,(void*)&status);
	printf("assistthread's exit is cause %d\n",status);
	
	return 0;
}

運行結果:

gcc -o jointhread jointhread.c -lpthread #編譯
svauto@ubuntua:~/C/thread$ ./jointhread  #運行
I am helping to do some jobs
assistthread's exit is cause 0

程序可以看出主線程在執行pthread_join後處於等待狀態,知道子線程退出後,才退出主線程。

四、私有數據

在多線程中,進程的數據空間是共享的,有時候線程需要有自己的私有全局數據,應該怎麼做呢?
線程私有數據使用:“一鍵多值”技術。創建“鍵”的時候,Linux從TSD池中分配一項,當一個鍵被創建後,所有的線程都可以訪問,但是不同線程從這個鍵中取到的值是不一樣的。這就是一鍵多值的含義。
涉及到的函數有四個

函數 作用
pthread_key_create 創建一個鍵
pthread_setspecific 爲一個鍵設置線程私有數據
pthread_getspecific 從一個鍵獲取私有數據
pthread_key_delete 刪除一個鍵

實例代碼:

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

pthread_key_t key;

void * thread2(void *arg)
{
	int tsd=5;
	
	printf("thread %x is running \n",pthread_self());
	pthread_setspecific(key,(void *)tsd);
	printf("thread %x returns %d \n",pthread_self(),pthread_getspecific(key));
}

void *thread1(void *arg)
{
	int tsd = 0;
	pthread_t thid2;
	
	printf("thread %x is running \n",pthread_self());
	pthread_setspecific(key,(void *)tsd);
	pthread_create(&thid2,NULL,thread2,NULL);
	sleep(2);
	printf("thread %x returns %d\n",pthread_self(), pthread_getspecific(key));
	
}

int main(void)
{
	pthread_t thid1;
	
	printf("main thread begins running\n");
	pthread_key_create(&key,NULL);
	pthread_create(&thid1,NULL,thread1,NULL);
	sleep(3);
	pthread_key_delete(key);
	printf("main thread exit\n");
	
	return 0;
}

運行結果:
在這裏插入圖片描述
可以看出兩個線程的私有數據,是不會互相影響的。

五、線程同步

linux有3種方式處理線程的同步:

  • 互斥鎖
  • 條件變量
  • 異步信號

1.互斥鎖
在同一時刻,通常只允許一個線程執行一個關鍵部分的代碼。涉及到的函數如下

函數 功能
pthread_mutex_init 初始化一個互斥鎖
pthread_mutex_destroy 註銷一個互斥鎖
pthread_mutex_lock 加鎖,如果不成功則阻塞等待
pthread_mutex_unlock 解鎖
pthread_mutex_trylock 測試加鎖,如果不成功則立即返回,錯誤碼爲EBUSY

互斥鎖初始化
初始化方法有兩種
在這裏插入圖片描述
實例代碼
下面代碼用互斥鎖的方式,保護全局變量在各線程之間同步(注意:此時不能在代碼中直接操作全局變量,而應該調用下面的read ,write函數)

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

pthread_mutex_t number_mutex;
int globalnumber;

void write_globalnumber()
{
	pthread_mutex_lock(&number_mutex);
	globalnumber++;
	printf("now globalnumber=%d\n",globalnumber);
	pthread_mutex_unlock(&number_mutex);
	
}

int read_globalnumber()
{
	int temp;
	
	pthread_mutex_lock(&number_mutex);
	temp= globalnumber;
	pthread_mutex_unlock(&number_mutex);
	
	return (temp);
}

void * thread2(void *arg)
{
	int tsd=5,i=20;
	printf("thread2 %x is running \n",pthread_self());
	
	while(i>0)
	{
		printf("read =%d\n",read_globalnumber());
		i--;
		usleep(1);
	}
}

void *thread1(void *arg)
{
	int tsd = 0,i=10;
	pthread_t thid2;
	
	printf("thread1 %x is running \n",pthread_self());
	
	while(i>0)
	{
		write_globalnumber();
		i--;
		usleep(1);
		
	}	
}


int main(void)
{
	pthread_t thid1,thid2;
	int statue;
	
	printf("main thread begins running\n");

	pthread_create(&thid1,NULL,thread1,NULL);
	pthread_create(&thid2,NULL,thread2,NULL);

	
	
	pthread_join(thid1,&statue);
	printf("statue1=%d\n",statue);
	pthread_join(thid2,&statue);
	printf("statue2=%d\n",statue);
	printf("main thread exit\n");
	return 0;
}


程序運行結果:
在這裏插入圖片描述
每次運行的會不一樣。因爲線程的調度是受系統控制的,不是按順序調度的。一個有趣的現象是在這裏插入圖片描述
當數據變量是3,爲什麼輸出是2呢?因爲讀線程正準備打印2的時候被中斷了,然後執行了寫線程寫入3。所以,互斥鎖做的同步是保證加鎖的部分所用到的資源,在同一時刻只有一個線程能用到。如果把輸出語句放到加鎖的部分,就可以看到讀出來的和寫出來的,是按順序出來的。

2.條件變量
測試代碼

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void * thread1(void *arg)
{
	pthread_cleanup_push(pthread_mutex_unlock,&mutex);
	
	while(1)
	{
		printf("thread1 is running\n");
		pthread_mutex_lock(&mutex);
		printf("thread1 mutex\n");
		pthread_cond_wait(&cond,&mutex);
		printf("thread1 applied the condition\n");
		pthread_mutex_unlock(&mutex);
		sleep(4);
	}
	
	pthread_cleanup_pop(0);
}

void *thread2(void *arg)
{
	while(1)
	{
		printf("thread2 is running\n");
		pthread_mutex_lock(&mutex);
		printf("thread2 mutex\n");
		pthread_cond_wait(&cond,&mutex);
		printf("thread2 applied the condition\n");
		pthread_mutex_unlock(&mutex);
		sleep(1);
	}
}

int main()
{
	pthread_t tid1,tid2;
	
	printf("condition variable study\n");
	
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond,NULL);
	pthread_create(&tid1,NULL,(void *)thread1,NULL);
	pthread_create(&tid2,NULL,(void *)thread2,NULL);
	
	do{
		pthread_cond_signal(&cond);
	}while(1);
	
	sleep(50);
	pthread_exit(0);
}



在這裏插入圖片描述
上面代碼加了“pthread_cleanup_push(pthread_mutex_unlock,&mutex);”和“pthread_cleanup_pop(0);”這對函數,目的是解決線程結束時資源釋放的問題。因爲pthread_cond_wait函數屬於“取消點”。其實對於thread2也應該設置這對函數。他們在線程出現意外的時候,纔會使用到。具體,請看我另一篇關於“線程終止的博文”

3.異步信號

六、錯誤碼

錯誤碼定義在“error.h”以E開頭的宏定義。
常見的錯誤碼

錯誤碼 含義
ENOMEM 內存不足
EIO 輸入輸出錯誤

在這裏插入圖片描述

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