Linux C編程--線程操作1--線程概述和簡單的線程操作


關於linux線程


在許多經典的操作系統教科書中, 總是把進程定義爲程序的執行實例, 它並不執行什麼, 只是維護應用程序所需的各種資源. 而線程則是真正的執行實體. 
爲了讓進程完成一定的工作, 進程必須至少包含一個線程. 如圖1.

進程所維護的是程序所包含的資源(靜態資源), 如: 地址空間, 打開的文件句柄集, 文件系統狀態, 信號處理handler, 等;
線程所維護的運行相關的資源(動態資源), 如: 運行棧, 調度相關的控制信息, 待處理的信號集, 等;



然而, 一直以來, linux內核並沒有線程的概念. 每一個執行實體都是一個task_struct結構, 通常稱之爲進程. 如圖2.

進程是一個執行單元, 維護着執行相關的動態資源. 同時, 它又引用着程序所需的靜態資源.
通過系統調用clone創建子進程時, 可以有選擇性地讓子進程共享父進程所引用的資源. 這樣的子進程通常稱爲輕量級進程. 

linux上的線程就是基於輕量級進程, 由用戶態的pthread庫實現的.
使用pthread以後, 在用戶看來, 每一個task_struct就對應一個線程, 而一組線程以及它們所共同引用的一組資源就是一個進程.

但是, 一組線程並不僅僅是引用同一組資源就夠了, 它們還必須被視爲一個整體.
對此, POSIX標準提出瞭如下要求:
1, 查看進程列表的時候, 相關的一組task_struct應當被展現爲列表中的一個節點;
2, 發送給這個"進程"的信號(對應kill系統調用), 將被對應的這一組task_struct所共享, 並且被其中的任意一個"線程"處理;
3, 發送給某個"線程"的信號(對應pthread_kill), 將只被對應的一個task_struct接收, 並且由它自己來處理;
4, 當"進程"被停止或繼續時(對應SIGSTOP/SIGCONT信號), 對應的這一組task_struct狀態將改變;
5, 當"進程"收到一個致命信號(比如由於段錯誤收到SIGSEGV信號), 對應的這一組task_struct將全部退出;
6, 等等(以上可能不夠全);


linuxthreads

在linux 2.6以前, pthread線程庫對應的實現是一個名叫linuxthreads的lib. 
linuxthreads利用前面提到的輕量級進程來實現線程, 但是對於POSIX提出的那些要求, linuxthreads除了第5點以外, 都沒有實現(實際上是無能爲力):
1, 如果運行了A程序, A程序創建了10個線程, 那麼在shell下執行ps命令時將看到11個A進程, 而不是1個(注意, 也不是10個, 下面會解釋);
2, 不管是kill還是pthread_kill, 信號只能被一個對應的線程所接收;
3, SIGSTOP/SIGCONT信號只對一個線程起作用;

還好linuxthreads實現了第5點, 我認爲這一點是最重要的. 如果某個線程"掛"了, 整個進程還在若無其事地運行着, 可能會出現很多的不一致狀態. 進程將不是一個整體, 而線程也不能稱爲線程. 
或許這也是爲什麼linuxthreads雖然與POSIX的要求差距甚遠, 卻能夠存在, 並且還被使用了好幾年的原因吧~
但是, linuxthreads爲了實現這個"第5點", 還是付出了很多代價, 並且創造了linuxthreads本身的一大性能瓶頸.

接下來要說說, 爲什麼A程序創建了10個線程, 但是ps時卻會出現11個A進程了. 因爲linuxthreads自動創建了一個管理線程. 上面提到的"第5點"就是靠管理線程來實現的.
當程序開始運行時, 並沒有管理線程存在(因爲儘管程序已經鏈接了pthread庫, 但是未必會使用多線程). 
程序第一次調用pthread_create時, linuxthreads發現管理線程不存在, 於是創建這個管理線程. 這個管理線程是進程中的第一個線程(主線程)的兒子.
然後在pthread_create中, 會通過pipe向管理線程發送一個命令, 告訴它創建線程. 即是說, 除主線程外, 所有的線程都是由管理線程來創建的, 管理線程是它們的父親.
於是, 當任何一個子線程退出時, 管理線程將收到SIGUSER1信號(這是在通過clone創建子線程時指定的). 管理線程在對應的sig_handler中會判斷子線程是否正常退出, 如果不是, 則殺死所有線程, 然後自殺.
那麼, 主線程怎麼辦呢? 主線程是管理線程的父親, 其退出時並不會給管理線程發信號. 於是, 在管理線程的主循環中通過getppid檢查父進程的ID號, 如果ID號是1, 說明父親已經退出, 並把自己託管給了init進程(1號進程). 這時候, 管理線程也會殺掉所有子線程, 然後自殺. 那麼, 如果主線程是調用pthread_exit主動退出的呢? 按照posix的標準,這種情況下其他子線程是應該繼續運行的. 於是, 在linuxthreads中, 主線程調用pthread_exit以後並不會真正退出, 而是會在pthread_exit函數中阻塞等待所有子線程都退出了, pthread_exit纔會讓主線程退出. (在這個等等過程中, 主線程一直處於睡眠狀態.)

可見, 線程的創建與銷燬都是通過管理線程來完成的, 於是管理線程就成了linuxthreads的一個性能瓶頸. 

創建與銷燬需要一次進程間通信, 一次上下文切換之後才能被管理線程執行, 並且多個請求會被管理線程串行地執行.


注:以上的這些描述來自百度空間--

kouu's home


線程管理初步

1.創建線程和結束線程

首先,我們需要了解下線程的引用標示符--pthread_t,這是一個命名別名,完整的定義如下:

typedef unsigned long int pthread_t;


下面介紹關於線程操作的相關係統函數

線程標識
  線程ID
    •進程ID在整個系統中是唯一的
    •線程ID只在它所屬的進程環境中有效
函數: pthread_self()

線程標識
  pthread_t類型通常用結構來表示
  •不能把它作爲整數處理
    –Linux使用無符號長整數表示
  •爲了移植,使用函數來比較線程ID
函數: pthread_equal()

創建線程
  •調用該線程函數的入口點
  •使用函數pthread_create(),線程創建後,就開始運行相關的線程函數

退出線程
  •在線程函數運行完後,該線程也就退出了
  •或使用函數pthread_exit(),這是線程的主動行爲
  •不能使用exit()

使調用進程終止,所有線程都終止了

等待線程

  •由於一個進程中的多個線程是共享數據段的,通常在線程退出之後,退出線程所佔用的資源並不會隨着線程的終止而得到釋放

  •pthread_join()函數

    類似進程的wait()/waitpid()函數,用於將當前線程掛起來等待線程的結束
    是一個線程阻塞的函數,調用它的線程一直等待到被等待的線程結束爲止
    函數返回時,被等待線程的資源就被收回

取消線程

  •在別的線程中要終止另一個線程
  •pthread_cancel()函數
  •被取消的線程可以設置自己的取消狀態
    –被取消的線程接收到另一個線程的取消請求之後,是接受還是忽略這個請求
    –如果接受,是立刻進行終止操作還是等待某個函數的調用等


下面給出兩個實例說明上述函數的用法。

1.程序中使用兩個線程,一個打印自己的線程ID和”Hello“,一個打印自己的線程ID和”World“
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void print_msg(char *ptr);

int main()
{
	pthread_t thread1, thread2;
	int i,j;
	char *msg1="Hello\n";
	char *msg2="World\n";
	pthread_create(&thread1,NULL, (void *)(&print_msg), (void *)msg1);
	pthread_create(&thread2,NULL, (void *)(&print_msg), (void *)msg2);
	sleep(1);
	return 0;
}

void  print_msg(char *ptr)
{
	int retval;
	int id=pthread_self();
	printf("Thread ID: %lx\n",id);
  	printf("%s",ptr);
	pthread_exit(&retval);
}

這個程序並不完善,其中包含許多線程同步的問題需要解決。
用gcc編譯多線程程序時,必須與pthread函數庫連接,在終端下編譯需要使用如下的命令
gcc -lpthread -o brucethread brucethread.c

2.pthread_join函數的使用
#include <stddef.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void print_msg(char *ptr);

int main()
{
	pthread_t thread1, thread2;
	int i,j;
	void *retval;
	char *msg1="Hello\n";
	char *msg2="World\n";
	pthread_create(&thread1,NULL, (void *)(&print_msg), (void *)msg1);
	pthread_create(&thread2,NULL, (void *)(&print_msg), (void *)msg2);
	pthread_join(thread1,&retval);
	pthread_join(thread2,&retval);
	return 0;
}

void  print_msg(char *ptr)
{
	int i;
	for(i=0;i<10000;i++)
  	printf("%s",ptr);
}

在兩個線程結束運行後,主進程才退出執行。

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