操作系統多線程編程基礎

一. 線程的基本操作函數

先講述4個基本線程函數,在調用它們前均要包括pthread.h頭文件

1.創建線程函數

int pthread_create(pthread_t *tid,const pthread_attr_t *attr, void *(*func)(void *),void *arg);

注意:

?? 第一個參數為指向線程標識符的指針

?? 第二個參數用來設置線程屬性

?? 第三個參數是線程運行函數的起始地址

?? 最后一個參數是運行函數的參數

函數thread 不需要參數,所以最后一個參數設為空指針;第二個參數我們也設為空指針,這樣將生成默認屬性的線程。創建線程成功后,新創建的線程則運行參數三和參數四確定的函數,原來的線程則繼續運行下一行代碼。<當創建線程成功時,函數返回0,若不為0則說明創建失敗,常見的錯誤返回代碼為EAGAIN和EINVAL>

2.等待線程的結束函數

int pthread_join(pthread_ttid,void**status);

第一個參數為被等待的線程標識符

第二個參數為一個用戶定義的指針,它可以用來存儲被等待線程的返回值

這個函數是一個線程阻塞的函數,調用它的函數將一直等待到被等待的線程結束為止,當函數返回時,被等待線程的資源被收回。

3.取自己線程ID函數

pthread_t pthread_self(void);

? 線程都有一個ID在給定的進程內標識自己。 線程ID由pthread_creat返回,可以取得自己的線程ID。

4.終止線程函數

一個線程的結束有兩種途徑,一種是函數結束,調用它的線程也就結束,另一種方式是通過函數pthread_exit 來實現。

void pthread_exit(void *status);

注:一個線程不能被多個線程等待,否則第一個接收到信號的線程成功返回,其余調用pthread_join 的線程則返回錯誤代碼ESRCH。

唯一的參數是函數的返回代碼

編譯程序:gcc –o target source –lpthread


二.簡單的多線程編程

舉例

#include<stdio.h>

#include<pthread.h>

void thread(void)

{

? ? int i;

? ? for(i=0;i<3;i++)

? ? printf("This is a pthread.\n");

}

運行程序,得到如下結果:

This is the main process。

This is a pthread。

This is the main process。

This is the main process.

This is a pthread.

This is a pthread。

再次運行,可能得到如下結果:

This is a pthread.

This is the main process.

This is a pthread.

This is the main process.

This is a pthread.

This is the main process.

前后兩次結果不一樣,這是兩個線程爭奪CPU資源的最終結果。



線程應用中的同步問題

許多函數是不可重入的,即同時不能運行一個函數的多個拷貝

在函數中聲明的靜態變量常常會帶來一些問題,函數的返回值也會有問題

進程中共享的變量必須用關鍵字volatile來定義

為了保護變量,必須使用信號量、互斥等方法來保證對變量的正確使用



互斥鎖

互斥鎖用來保證一段時間內只有一個線程在執行一段代碼。

先看下面一段代碼這是一個讀/寫程序,它們公用一個緩沖區,并且假定一個緩沖區只能保存一條信息。即緩沖區只有兩個狀態:有信息或沒有信息。

void reader_function(void);

void writer_function(void);

char buffer;

int buffer_has_item=0;

pthread_mutex_t mutex;//這里聲明了互斥鎖變量mutex,結構pthread_mutex_t為不公開的數據類型,其中包含一個系統分配的屬性對象

struct timespec delay;


void main(void)

{

? ?? pthread_t reader;

? ?? delay.tv_sec=2;/*定義延遲時間*/

? ?? delay.tv_nec=0;

? ?? pthread_mutex_init(&mutex,NULL);

? ?? /*用默認屬性初始化一個互斥鎖對象*/?

? ?? pthread_create(&reader,pthread_attr_default,(void *)&reader_function,NULL);

? ?? writer_function();

? }


void writer_function(void){

? ?? while(1) {

? ? ? ? ? pthread_mutex_lock(&mutex);/*鎖定互斥鎖*/

? ? ? ? ? if(buffer_has_item==O){

? ? ? ? ? ? ? ? buffer=make_new_item();

? ? ? ? ? ? ? ? buffer_has_item=1;

? ? ? ? ?? }

? ? ? ?? pthread_mutex_unlock(&mutex);/*打開互斥鎖*/

? ? ? ?? pthreal_delay_np(&delay);

? ? ? }

?}


void reader_function(void)

{

? ?? while(1){

? ? ? ? ?? pthread_mutex_lock(&mutex);

? ? ? ? ?? If(buffer_has_item==1){

? ? ? ? ? ? ? ? consume_item(buffer);

? ? ? ? ? ? ? ? buffer_has_item=O;

? ? ? ? ?? }

? ? ? ? ?? pthread_mutex_unlock(&mutex);

? ? ? ? ?? pthread_delay_np(&delay);

? ? ?? }

}

鎖的聲明:pthread_mutex_t mutex;

鎖的初始化:pthread_mutex_init(&mutex,NULL);

pthread_mutex_lock 聲明開始用互斥鎖上鎖,此后的代碼直至調用pthread_mutex_unlock為止均被上鎖,即同一時間只能被一個線程調用執行。

當一個線程執行到pthread_mutex_lock處時,如果此時另一個線程使用該鎖,則將阻塞此線程,即程序將等待到另一個線程釋放此互斥鎖。

pthread_mutex_destroy(phtread_mutex_t * lock);


pthread_delay_np函數,讓線程睡眠一段時間,

是為了防止一個線程始終占據此函數。


注意:在使用互斥鎖的過程中很有可能會出現死鎖:即兩個線程試圖同時占用兩個資源,并按不同的次序鎖定相應的互斥鎖。此時我們可以使用函數pthread_mutex_trylock,它是函數pthread_mutex_lock的非阻塞版本,當它發現死鎖不可避免時,它會返回相應的信息,程序員可以針對死鎖做出相應的處理。



讀寫鎖

讀寫鎖是從互斥鎖中發展下來的,互斥鎖會將試圖訪問我們定義的保護區的所有進程都阻塞掉,但讀寫鎖與此不同,它會將訪問中的讀操作和寫操作區分開來對待

在某些讀數據比改數據頻繁的應用中,讀寫鎖將會比互斥鎖表現出很大的優越性


讀寫鎖所遵循的規則:

只要沒有進程持有某個給定的讀寫鎖用于寫,那么任意數目的線程都可持有該讀寫鎖用于讀

僅當沒有線程持有某個給定的讀寫鎖用于讀或寫,才能分配該讀寫鎖用于寫。


讀寫鎖的聲明:pthread_rwlock_t? rwlock;

讀寫鎖初始化:pthread_rwlock_init (pthread_rwlock_t *rwlock,? pthread_rwlockattr_t *attr);

例: pthread_rwlock_init (&rwlock,NULL);


為讀進程獲得鎖:pthread_rwlock_rdlock(pthread_rwlock_t *lock);

為寫進程獲得鎖:pthread_rwlock_wrlock(pthread_rwlock_t *lock);

讀寫進程解鎖:pthread_rwlock_unlock(pthread_rwlock_t *lock);

銷毀讀寫鎖:pthread_rwlock_destroy(pthread_rwlock_t *lock);

上述函數執行成功,返回0,如果不為0,則函數執行失敗



條件變量

條件變量是用來等待而不是用來上鎖的。它是通過一種能夠掛起當前正在執行的進程或放棄當前進程直到在共享數據上的一些條件得以滿足。

條件變量操作過程是:首先通知條件變量,然后等待,同時掛起當前進程直到有另外一個進程通知該條件變量為止。

互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。

工作機制:條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,并釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程間的線程同步。?

條件變量的聲明Pthread_cond_t? condtion;

條件變量的初始化pthread_cond_init(pthread_cond_t * cond,pthread_condattr_t *attr);

等待信號pthread_cond_wait(pthread_cond_t * cond,pthread_mutex_t * mutex);

發送信號pthread_cond_signal(pthread_cond_t *cond);



信號量

信號量(semaphore)是另一種加鎖操作,與普通加鎖不同的是,信號量記錄了一個空閑資源數值,信號量的值表示了當前空閑資源的多少。信號量本質上是一個非負的整數計數器,它用來控制對公共資源的訪問。


當公共資源增加時,調用函數sem_post()增加信號量

當信號量值大于0時,才能使用公共資源,使用后,函數sem_wait()減少信號量

當它變為0時,進程將主動放棄處理器進入等待對列


sem_init(sem_t *sem, int pshared, unsigned int val)

這個函數初始化一個信號量sem 的值為val,參數pshared 是共享屬性控制,表明是否在進程間共享。

sem_wait(sem_t *sem);

調用該函數時,若sem為無狀態,調用線程阻塞,等待信號量sem值增加(post )成為有信號狀態;若sem為有狀態,調用線程順序執行,但信號量的值減一。

sem_post(sem_t *sem);

調用該函數,信號量sem的值增加,可以從無信號狀態變為有信號狀態。

sem_destory(sem_t *sem);?

釋放信號量。

三.實驗

事先編輯好數據文件1.dat和2.dat,假設它們的內容分別為1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 , 設計一個程序,在這個程序中一共有3個線程,其中兩個線程負責從文件讀取數據到公共的緩沖區,另外一個線程從緩沖區讀取數據作不同的處理(加和乘運算)。

具體要求:

線程1從1.dat將數據讀文件讀到buf1中;

線程2從2.dat 將數據讀到buf2中;

當buf1,buf2有數據時,線程3將buf1和buf2的結果相加和乘法處理,并將結果顯示出來;

實驗代碼

代碼取自https://blog.csdn.net/qq_42316621/article/details/101291603,侵刪

#include <stdio.h>

#include <pthread.h>

#include <semaphore.h>

int buf1,buf2;

sem_t sem1;//是否可以向buf1中讀入數據

sem_t sem2;//是否可以向buf2中讀入數據

sem_t sem3;//是否可以從buf1中取出數據

sem_t sem4;//是否可以從buf2中取出數據


/*?從1.dat將數據讀文件讀到buf1中 */

void ReadData1(void)

{

? FILE *fp=fopen("1.dat","r");

? while(!feof(fp)){

? ? sem_wait(&sem1);

? ? fscanf(fp,"%d",&buf1);

? ? sem_post(&sem3);

? }

? fclose(fp);

}


/*?從2.dat 將數據讀到buf2中 */

void ReadData2(void)

{

? FILE *fp=fopen("2.dat","r");

? while(!feof(fp)){

? ? sem_wait(&sem2);

? ? fscanf(fp,"%d",&buf2);

? ? sem_post(&sem4);

? }

? fclose(fp);

}


/* 計算 */

void HandleData(void)

{

? while(1){

? ? sem_wait(&sem3);

? ? sem_wait(&sem4);

? ? printf("Pluse: %d+%d=%d\n",buf1,buf2,buf1+buf2);

? ? printf("Multiply: %d*%d=%d\n",buf1,buf2,buf1*buf2);

? ? sem_post(&sem1);

? ? sem_post(&sem2);

? }

}

void main()

{

? pthread_t t1,t2,t3;

? sem_init(&sem1,0,1);

? sem_init(&sem2,0,1);

? sem_init(&sem3,0,0);

? sem_init(&sem4,0,0);

? pthread_create(&t1,NULL,(void *)ReadData1,NULL);

? pthread_create(&t2,NULL,(void *)ReadData2,NULL);

? pthread_create(&t3,NULL,(void *)HandleData,NULL);

? pthread_join(t1,NULL);

}

實驗結果

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容