線程:可以理解爲輕量級的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 | 輸入輸出錯誤 |