Linux系統編程---多線程編程之線程、互斥鎖和條件變量
Linux系統編程—多線程編程之線程、互斥鎖和條件變量
一、進程與線程的區別解析
典型的UNIX/Linux進程可以看成只有一個控制線程:一個進程在同一時刻只做一件事情。有了多個控制線程后,在程序設計時可以把進程設計成在同一時刻做不止一件事,每個線程各自處理獨立的任務。
(1)線程與進程
進程是程序執行時的一個實例,是擔當分配系統資源(CPU時間、內存等)的基本單位。
在面向線程設計的系統中,進程本身不是基本運行單位,而是線程的容器。
程序本身只是指令、數據及其組織形式的描述,進程才是程序(那些指令和數據)的真正運行實例。
進程——資源分配的最小單位,線程——程序執行的最小單位"
進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程沒有單獨的地址空間,一個線程死掉就等于整個進程死掉,所以多進程的程序要比多線程的程序健壯,但在進程切換時,耗費資源較大,效率要差一些。但對于一些要求同時進行并且又要共享某些變量的并發操作,只能用線程,不能用進程。
(2)使用線程的理由(面試會問的)
使用多線程的理由之一是和進程相比,它是一種非常"節儉"的多任務操作方式。我們知道,在Linux系統下,啟動一個新的進程必須分配給它獨立的地址空間,建立眾多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工作方式。而運行于一個進程中的多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啟動一個線程所花費的空間遠遠小于啟動一個進程所花費的空間,而且,線程間彼此切換所需的時間也遠遠小于進程間切換所需要的時間。據統計,總的說來,一個進程的開銷大約是一個線程開銷的30倍左右,當然,在具體的系統上,這個數據可能會有較大的區別。
使用多線程的理由之二是線程間方便的通信機制。對不同進程來說,它們具有獨立的數據空間,要進行數據的傳遞只能通過通信的方式進行,這種方式不僅費時,而且很不方便。線程則不然,由于同一進程下的線程之間共享數據空間,所以一個線程的數據可以直接為其它線程所用,這不僅快捷,而且方便。當然,數據的共享也帶來其他一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數據更有可能給多線程程序帶來災難性的打擊,這些正是編寫多線程程序時最需要注意的地方。
除了以上所說的優點外,不和進程比較,多線程程序作為一種多任務、并發的工作方式,當然有以下的優點:
提高應用程序響應。這對圖形界面的程序尤其有意義,當一個操作耗時很長時,整個系統都會等待這個操作,此時程序不會響應鍵盤、鼠標、菜單的操作,而使用多線程技術,將耗時長的操作(time consuming)置于一個新的線程,可以避免這種尷尬的情況。
使多CPU系統更加有效。操作系統會保證當線程數不大于CPU數目時,不同的線程運行于不同的CPU上。
改善程序結構。一個既長又復雜的進程可以考慮分為多個線程,成為幾個獨立或半獨立的運行部分,這樣的程序會利于理解和修改。
(本部分摘自http://www.cnblogs.com/skynet/archive/2010/10/30/1865267.HTML)
二、與線程自身相關API
1. 線程創建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否則返回錯誤編號
當pthread_create成功返回時,由tidp指向的內存單元被設置為新創建線程的線程ID。attr參數用于定制各種不同的線程屬性,暫可以把它設置為NULL,以創建默認屬性的線程。
新創建的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針參數arg。如果需要向start_rtn函數傳遞的參數不止一個,那么需要把這些參數放到一個結構中,然后把這個結構的地址作為arg參數傳入。
2. 線程退出
單個線程可以通過以下三種方式退出,在不終止整個進程的情況下停止它的控制流:
1)線程只是從啟動例程中返回,返回值是線程的退出碼。
2)線程可以被同一進程中的其他線程取消。
3)線程調用pthread_exit
#include <pthread.h>
int pthread_exit(void *rval_ptr);
rval_ptr是一個無類型指針,與傳給啟動例程的單個參數類似。進程中的其他線程可以通過調用pthread_join函數訪問到這個指針。
3. 線程等待
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否則返回錯誤編號
調用這個函數的線程將一直阻塞,直到指定的線程調用pthread_exit、從啟動例程中返回或者被取消。如果例程只是從它的啟動例程返回i,rval_ptr將包含返回碼。如果線程被取消,由rval_ptr指定的內存單元就置為PTHREAD_CANCELED。
可以通過調用pthread_join自動把線程置于分離狀態,這樣資源就可以恢復。如果線程已經處于分離狀態,pthread_join調用就會失敗,返回EINVAL。
如果對線程的返回值不感興趣,可以把rval_ptr置為NULL。在這種情況下,調用pthread_join函數將等待指定的線程終止,但并不獲得線程的終止狀態4
4. 線程ID獲取及比較
#include <pthread.h>
pthread_t pthread_self(void);
// 返回:調用線程的ID
對于線程ID比較,為了可移植操作,我們不能簡單地把線程ID當作整數來處理,因為不同系統對線程ID的定義可能不一樣。我們應該要用下邊的函數:
#include <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2);
// 返回:若相等則返回非0值,否則返回0
線程編寫代碼dmeo1.c如下:
#include <pthread.h>
#include <stdio.h>
//int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
void *fun1(void *arg)
{
printf("t1 pthread:%ld\n",(unsigned long)pthread_self()); //打印出線程1的ID號
//pthread_t pthread_self (void);
printf("t1 arg:%d\n",*(int *)arg); //打印t1 的arg 的值
}
void *fun2(void *arg)
{
printf("t2 pthread:%ld\n",(unsigned long)pthread_self());//打印出線程2的ID號
//pthread_t pthread_self (void);
printf("t2 arg:%d\n",*(int *)arg);
}
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
int arg=100;
ret= pthread_create(&t1,NULL,fun1,(void *)&arg); //線程的創建
if(ret==0){
printf("mian :t1 pthread success\n"); //判斷線程是否創建成功
}
ret= pthread_create(&t2,NULL,fun2,(void *)&arg);//線程的創建
if(ret==0){
printf("mian :t2 pthread success\n");//判斷線程是否創建成功
}
printf("main pthread:%ld\n",(unsigned long)pthread_self()); //打印主線程的ID號
//int pthread_join (pthread_t tid, void ** status);
pthread_join(t1,NULL); //線程的等待
pthread_join(t2,NULL);
return 0;
}
運行結果:
線程共享內存空間代碼驗證:
#include <pthread.h>
#include <stdio.h>
//int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
int g_data=0;
void *fun1(void *arg)
{
printf("t1 pthread:%ld\n",(unsigned long)pthread_self());//打印出線程1的ID號
//pthread_t pthread_self (void);
printf("t1 arg:%d\n",*(int *)arg);
while(1){
printf("t1 data:%d\n",g_data++);
sleep(1);
}
}
void *fun2(void *arg)
{
printf("t2 pthread:%ld\n",(unsigned long)pthread_self());//打印出線程2的ID號
//pthread_t pthread_self (void);
printf("t2 arg:%d\n",*(int *)arg);
while(1){
printf("t2 data:%d\n",g_data++);
sleep(1);
}
}
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
int arg=100;
ret= pthread_create(&t1,NULL,fun1,(void *)&arg); //創建線程
if(ret==0){
printf("mian :t1 pthread success\n"); //判斷線程1是否創建成功
}
ret= pthread_create(&t2,NULL,fun2,(void *)&arg);
if(ret==0){
printf("mian :t2 pthread success\n");//判斷線程2是否創建成功
}
printf("main pthread:%ld\n",(unsigned long)pthread_self());
while(1){ //一直 循環下去
printf("main data:%d\n",g_data++);
sleep(1); //休眠1秒
}
//int pthread_join (pthread_t tid, void ** status);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
運行結果:程序一直運行下去(死循環)
三、互斥鎖API解析:
互斥量(mutex)從本質上來說是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成后釋放互斥量上的鎖。
互斥變量用pthread_mutex_t數據類型表示。在使用互斥變量前必須對它進行初始化,可以把它置為常量PTHREAD_MUTEX_INITIALIZER(只對靜態分配的互斥量),也可以通過調用pthread_mutex_init函數進行初始化。如果動態地分配互斥量(例如通過調用malloc函數),那么在釋放內存前需要調用pthread_mutex_destroy。
1. 創建及銷毀互斥鎖
函數原型:
include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
// 返回:若成功返回0,否則返回錯誤編號
要用默認的屬性初始化互斥量,只需把attr設置為NULL。
2. 加鎖及解鎖
函數原型:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex);
int pthread_mutex_trylock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
// 返回:若成功返回0,否則返回錯誤編號
如果線程不希望被阻塞,它可以使用pthread_mutex_trylock嘗試對互斥量進行加鎖。如果調用pthread_mutex_trylock時互斥量處于未鎖住狀態,那么
pthread_mutex_trylock將鎖住互斥量,不會出現阻塞并返回0,否則pthread_mutex_trylock就會失敗,不能鎖住互斥量,而返回EBUSY
代碼示例:
#include <pthread.h>
#include <stdio.h>
//int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
// int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
int g_data=0;
pthread_mutex_t mutex;
void *fun1(void *arg)
{
// int pthread_mutex_lock(pthread_mutex *mutex);
pthread_mutex_lock(&mutex); // 上鎖
printf("t1 pthread:%ld\n",(unsigned long)pthread_self());//pthread_t pthread_self (void);
printf("t1 arg:%d\n",*(int *)arg);
// int pthread_mutex_unlock(pthread_mutex *mutex);
pthread_mutex_unlock(&mutex); // 解鎖
}
void *fun2(void *arg)
{
pthread_mutex_lock(&mutex); //上鎖
printf("t2 pthread:%ld\n",(unsigned long)pthread_self());//pthread_t pthread_self (void);
printf("t2 arg:%d\n",*(int *)arg);
pthread_mutex_unlock(&mutex); //解鎖
}
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL); //創建一個鎖
int arg=100;
ret= pthread_create(&t1,NULL,fun1,(void *)&arg);
if(ret==0){
printf("mian :t1 pthread success\n");
}
ret= pthread_create(&t2,NULL,fun2,(void *)&arg);
if(ret==0){
printf("mian :t2 pthread success\n");
}
printf("main pthread:%ld\n",(unsigned long)pthread_self());
//int pthread_join (pthread_t tid, void ** status);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex); //銷毀鎖
return 0;
}
運行結果:
t1先運行的 因為t1先拿到了鎖,把自己鎖住了,下面的程序不能運行了,t1運行了完后解鎖,然后下面的程序可以運行了。
四、與條件變量相關API
條件變量是線程另一可用的同步機制。條件變量給多個線程提供了一個會合的場所。條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發生。
條件本身是由互斥量保護的。線程在改變條件狀態前必須首先鎖住互斥量,其他線程在獲得互斥量之前不會察覺到這種改變,因為必須鎖定互斥量以后才能計算條件。
條件變量使用之前必須首先初始化,pthread_cond_t數據類型代表的條件變量可以用兩種方式進行初始化,可以把常量PTHREAD_COND_INITIALIZER賦給靜態分配的條件變量,但是如果條件變量是動態分配的,可以使用pthread_cond_destroy函數對條件變量進行去除初始化(deinitialize)。
1. 創建及銷毀條件變量
函數原型:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t cond);
// 返回:若成功返回0,否則返回錯誤編號
除非需要創建一個非默認屬性的條件變量,否則pthread_cont_init函數的attr參數可以設置為NULL。
2. 等待
函數原型:
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
// 返回:若成功返回0,否則返回錯誤編號
pthread_cond_wait等待條件變為真。如果在給定的時間內條件不能滿足,那么會生成一個代表一個出錯碼的返回變量。傳遞給pthread_cond_wait的互斥量對條件進行保護,調用者把鎖住的互斥量傳給函數。函數把調用線程放到等待條件的線程列表上,然后對互斥量解鎖,這兩個操作都是原子操作。這樣就關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操作之間的時間通道,這樣線程就不會錯過條件的任何變化。pthread_cond_wait返回時,互斥量再次被鎖住。
pthread_cond_timedwait函數的工作方式與pthread_cond_wait函數類似,只是多了一個timeout。timeout指定了等待的時間,它是通過timespec結構指定。
3. 觸發
函數原型:
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond);
int pthread_cond_broadcast(pthread_cond_t cond);
// 返回:若成功返回0,否則返回錯誤編號
這兩個函數可以用于通知線程條件已經滿足。pthread_cond_signal函數將喚醒等待該條件的某個線程,而pthread_cond_broadcast函數將喚醒等待該條件的所有進程。
注意一定要在改變條件狀態以后再給線程發信號。
代碼示例:
#include <pthread.h>
#include <stdio.h>
//int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func) (void *), void *arg);
int g_data=0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *fun1(void *arg)
{
printf("t1 pthread:%ld\n",(unsigned long)pthread_self());//pthread_t pthread_self (void);
printf("t1 arg:%d\n",*(int *)arg);
while(1){
pthread_cond_wait(&cond,&mutex); //等待上個程序結束
printf("t1 sun===========\n");
printf("t1 data=%d\n",g_data);
g_data=0;
sleep(1); //休眠一秒
}
}
void *fun2(void *arg)
{
printf("t2 pthread:%ld\n",(unsigned long)pthread_self());
//pthread_t pthread_self (void);
printf("t2 arg:%d\n",*(int *)arg);
while(1){
printf("t2 data:%d\n",g_data);
pthread_mutex_lock(&mutex); //上鎖
g_data++;
if(g_data==4){
pthread_cond_signal(&cond); //觸發等待的程序運行
}
pthread_mutex_unlock(&mutex); //解鎖
sleep(1);
}
}
int main()
int main()
{
int ret;
pthread_t t1;
pthread_t t2;
int arg=100;
pthread_mutex_init(&mutex,NULL); //創建一個鎖
pthread_cond_init(&cond,NULL); // 創建條件變量
ret= pthread_create(&t1,NULL,fun1,(void *)&arg); //創建線程1
ret= pthread_create(&t2,NULL,fun2,(void *)&arg); //創建線程2
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex); //銷毀鎖
pthread_cond_destroy(&cond); //銷毀條件變量
return 0;
}
運行結果:
程序跑起來的時候,t1會阻塞著,等待一個命令的到來,t1程序就會跑起來
智能推薦
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...
19.vue中封裝echarts組件
19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...
猜你喜歡
【一只蒟蒻的刷題歷程】【藍橋杯】歷屆試題 九宮重排 (八數碼問題:BFS+集合set)
資源限制 時間限制:1.0s 內存限制:256.0MB 問題描述 如下面第一個圖的九宮格中,放著 1~8 的數字卡片,還有一個格子空著。與空格子相鄰的格子中的卡片可以移動到空格中。經過若干次移動,可以形成第二個圖所示的局面。 我們把第一個圖的局面記為:12345678. 把第二個圖的局面記為:123.46758 顯然是按從上到下,從左到右的順序記錄數字,空格記為句點。 本題目的任務是已知九宮的初態...
dataV組件容器寬高發生變化后,組件不會自適應解決方法
項目中需要大屏幕數據展示,于是使用了dataV組件,但是使用是發現拖動瀏覽器邊框,dataV組件顯示異常,如圖: 于是查了官網,官網的解釋如下: 于是按照官網的意思編寫代碼: 于是可以自適應了...
CSS3干貨10:如何做一個板塊標題水平線左邊帶顏色效果
很多網站在設計欄目標題的時候,喜歡用下劃線分開欄目標題和內容部分。 而且線條左邊的部分往往還有顏色,且這個顏色跟標題的文字長短保持一致。效果如圖所示: 這種效果其實很簡單。 我這里給大家推薦兩種方式: 假定我們的標題部分 HTML 結構如下: 方式一:利用下邊框。灰色部分是 h1 的下邊框,藍色部分是 span 標簽的下邊框。 h1 的高度為 40px,span 也設置它的高度為 40px。這樣,...
拜師————python基礎入門——程序的構成,對象,引用,棧內存和堆內存,標識符命名規則——day4
第九節課:任務9:009.程序的構成 Python程序的構成,一個程序是由什么構成的 1.python程序由模塊組成 , 一個模塊對應一個python源文件,(文件后綴名.py) 2.模塊由語句構成 運行程序時,安裝模塊中語句的順序依次執行。 代碼的組織和縮進 “龜叔”在設計python時,直接通過縮進來組織代碼 縮進時,幾個空格都是允許的,但是空格數必須統一,我們通常用四...