線程控制
操作系統并沒有提供創建線程的系統調用接口,因此大佬們封裝了一個線程的接口庫實現線程控制。意為著用戶創建線程都使用的是庫函數(所以有時候我們說創建的線程是一個用戶態線程,但是在內核中對應有一個輕量級進程實現線程程序的調度)
進程ID == 線程組id–tgid == 主線程pid
線程控制函數
pthread_self 函數
獲取線程 ID。其作用對應進程中 getpid() 函數。
pthread_t pthread_self(void);
返回值:成功:0; 失敗:無!
線程 ID:pthread_t 類型,本質:在 Linux 下為無符號整數(%lu),其他系統中可能是結構體實現
線程 ID 是進程內部,識別標志。(兩個進程間,線程 ID 允許相同)
注意:不應使用全局變量 pthread_t tid
,在子線程中通過 pthread_create
傳出參數來獲取線程 ID,而應使用 pthread_self
。
pthread_create 函數
創建一個新線程。 其作用,對應進程中 fork() 函數。
int pthread_create (pthread_t * thread,const pthread_attr_t *attr,void* (*start_routine)(void*),void* arg);
返回值:成功:0; 失敗:錯誤號 -----Linux 環境下,所有線程特點,失敗均直接返回錯誤號。
參數:
pthread_t:當前 Linux 中可理解為:typedef unsigned long int pthread_t;
參數 1:傳出參數,保存系統為我們分配好的線程 ID
參數 2:通常傳 NULL,表示使用線程默認屬性。若想使用具體屬性也可以修改該參數。
參數 3:函數指針,指向線程主函數(線程體),該函數運行結束,則線程結束。
參數 4:線程主函數執行期間所使用的參數。
在一個線程中調用 pthread_create()創建新的線程后,當前線程從 pthread_create()返回繼續往下執行,而新的線 程所執行的代碼由我們傳給 pthread_create 的函數指針 start_routine 決定。
start_routine 函數接收一個參數,是通過 pthread_create 的 arg 參數傳遞給它的,該參數的類型為 void *
,這個指針按什么類型解釋由調用者自己定義。
start_routine 的返回值類型也是 void*
,這個指針的含義同樣由調用者自己定義。start_routine 返回時,這個線程就 退出了,其它線程可以調用 pthread_join 得到 start_routine 的返回值,類似于父進程調用 wait(2)得到子進程的退出 狀態,稍后詳細介紹 pthread_join。
pthread_create 成功返回后,新創建的線程的 id 被填寫到 thread 參數所指向的內存單元。我們知道進程 id 的類 型是 pid_t,每個進程的 id 在整個系統中是唯一的,調用 getpid(2)可以獲得當前進程的 id,是一個正整數值。
線程 id 的類型是 thread_t,它只在當前進程中保證是唯一的,在不同的系統中 thread_t 這個類型有不同的實現,它可能 是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf打印,調用pthread_self(3) 可以獲得當前線程的 id。
pthread_t到底是什么類型呢?取決于實現。對于Linux目前實現的NPTL實現而言,pthread_t類型的線程ID,本質就 是一個進程地址空間上的一個地址。線程空間的首地址
練習:創建一個新線程,打印線程 ID。注意:鏈接線程庫 -lpthread
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>void *thrd_func(void *arg)
{//子線程的IDprintf(" In thread id = %lu,pid = %u\n",pthread_self(),getpid());return NULL;
}int main(void)
{pthread_t tid;int ret;//主控線程IDprintf(" In main1 id = %lu,pid = %u\n",pthread_self(),getpid());ret = pthread_create(&tid,NULL,thrd_func,NULL);if(ret != 0){ printf("pthread_create error\n");exit(1);} printf(" In main2 id = %lu,pid = %u\n",pthread_self(),getpid()); sleep(1);//主控線程等待子線程1秒,讓它打印return 0;
}
思考:
由于 pthread_create 的錯誤碼不保存在 errno 中,因此不能直接用 perror(3)打印錯誤信息,可以先用 strerror(3) 把錯誤碼轉換成錯誤信息再打印。如果任意一個線程調用了 exit 或_exit,則整個進程的所有線程都終止,由于從 main 函數 return 也相當于調用 exit,為了防止新創建的線程還沒有得到執行就終止,我們在 main 函數 return 之前 延時 1 秒,這只是一種權宜之計,即使主線程等待 1 秒,內核也不一定會調度新創建的線程執行。
循環創建多個線程,每個線程打印自己是第幾個被創建的線程。(類似于進程循環創建子進程)
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>void *thrd_func(void *arg)
{int i=(int)arg;sleep(i); //子線程的IDprintf("%d th In thread id = %lu,pid = %u\n", i+1 ,pthread_self(),getpid());return NULL;
}int main(void)
{pthread_t tid;int ret,i;for(i=0;i < 5; i++){ ret = pthread_create(&tid,NULL,thrd_func,(void *)i);if(ret != 0){ fprintf(stderr,"pthread_create error:%s\n",strerror(ret));exit(1);} } sleep(i);// 主控線程等待子線程1秒,讓它打印 return 0;// 將當前進程退出
}
注意:將 pthread_create
函數參 4 修改為(void*)&i
, 將線程主函數內改為 i=*((int*)arg)
不可以
線程間共享全局變量
驗證
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>int var =100;void *tfn(void * arg)
{//子線程修改varvar = 200;printf("thread\n");return NULL;
}int main(void)
{printf("At first var = %d\n",var);pthread_t tid;pthread_create(&tid,NULL,tfn,NULL);sleep(1);//等子線程退出后,再次打印var printf("after pthread_create, var = %d\n",var);return 0;
}
線程默認共享數據段、代碼段等地址空間,常用的是全局變量。而進程不共享全局變量,只能借助 mmap
pthread_exit 函數
將單個線程退出
void pthread_exit (void*retval)
; 參數:retval
表示線程退出狀態,通常傳 NULL
思考:使用 exit 將指定線程退出,可以嗎?
結論:線程中,禁止使用 exit 函數,會導致進程內所有線程全部退出。
在不添加 sleep 控制輸出順序的情況下。pthread_create 在循環中,幾乎瞬間創建 5 個線程,但只有第 1 個線程 有機會輸出(或者第 2 個也有,也可能沒有,取決于內核調度)如果第 3 個線程執行了 exit,將整個進程退出了, 所以全部線程退出了。
所以,多線程環境中,應盡量少用,或者不使用 exit 函數,取而代之使用 pthread_exit
函數,將單個線程退出。 任何線程里 exit 導致進程退出,其他線程未工作結束,主控線程退出時不能 return 或 exit。
另注意,pthread_exit 或者 return 返回的指針所指向的內存單元必須是全局的或者是用 malloc 分配的,不能在 線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。