企業(yè)網(wǎng)站建設(shè)費用定金怎么做賬關(guān)鍵詞查網(wǎng)址
多線程(三)
- 條件競爭
- 并發(fā)程序引起的共享內(nèi)存的問題
- 死鎖
- 互斥鎖機制
- 生產(chǎn)者消費者模型
- 信號量機制
- 解決:
條件競爭
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
void* Print(char* str){printf("%s ",str);
}int main(){pthread_t thread1,thread2;pthread_create(&thread1,NULL,(void*)&Print,"Hello");pthread_create(&thread2,NULL,(void*)&Print,"World");return 0;
}
編譯運行后,發(fā)現(xiàn)沒有預(yù)期輸出。
原因:主線程的退出會導(dǎo)致創(chuàng)建的線程退出
使用
pthread_create
函數(shù)創(chuàng)建兩個線程,兩個線程創(chuàng)建后,并不影響主線程的執(zhí)行,所以這里就存在三個線程的競爭關(guān)系。主線程執(zhí)行return 0;
先于另外兩個線程的打印函數(shù)。所以看不見另外兩個線程的輸出。為了使return 0;
語句在另外兩個進程后執(zhí)行,可以采用sleep()
函數(shù)進行延遲,就可以得到輸出了,這就是條件競爭。
在遇到條件競爭的問題中,采用sleep()
函數(shù)進行延遲似乎也能解決問題。實則不然,弊端很明顯:
- 不能判斷延遲的時間長度。
- 使程序執(zhí)行卡頓。
最適當(dāng)?shù)慕鉀Q方法是采用鎖機制。
并發(fā)程序引起的共享內(nèi)存的問題
有兩個進程,兩個進程共享全局變量s。兩個進程都執(zhí)行一個計數(shù)功能的函數(shù),直觀地看過去,th1運行時s++要執(zhí)行10000次,th2運行時s++也要執(zhí)行10000次,似乎計算得到的最后s應(yīng)該是20000。但實際上是這樣的嗎?
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
int s = 0;
void *func(void*args){int i;for(i = 0; i < 10000; i++){s++;}return NULL;
}
int main(){pthread_t th1;pthread_t th2;pthread_create(&th1, NULL, func, NULL);pthread_create(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("%s = %d\n", s);return 0;
}
編譯運行后,發(fā)現(xiàn)輸出并不是20000,而是12657。
運行了3次這個程序,每次的結(jié)果都不同。 用這個演示來表示一下,多線程之間是資源共享的。
s++ 是有三個步的,讀取s,s+1,寫入s。
在程序運行的某個時刻,th1攜帶myfunc執(zhí)行s++,讀取s,此時s=100,進行s+1, 與此同時th2也開始讀取s,此時的s還是等于100, 這時th1,執(zhí)行寫入s=101,th2執(zhí)行s++,寫入s ,s=101. th2中的s 就會覆蓋掉 th1中的s 。這樣造成了結(jié)果的誤差。
原因:當(dāng)我們執(zhí)行s++,底層發(fā)生的事件其實是:內(nèi)存中讀取s→將s+1→將s寫入到內(nèi)存。這不是一個原子化操作,當(dāng)兩個線程交錯運行的時候,很容易發(fā)生結(jié)果的丟失。因此最后的結(jié)果肯定是要小于20000的。這種情況有種專有名詞,叫race condition。
為了解決這個問題,我們可以加鎖。
#include <pthread.h>
int s = 0;
pthread_mutex_t lock; //鎖的聲明
void *func(void *args){int i;for(i = 0; i < 10000; i++){ //給臨界區(qū)代碼加鎖實現(xiàn)原子化操作pthread_mutex_lock(&lock);s++;pthread_mutex_unlock(&lock);}return NULL;
}
int main(){pthread_t th1;pthread_t th2;//鎖初始化pthread_mutex_init(&lock, NULL);pthread_create(&th1, NULL, func, NULL);pthread_createe(&th2, NULL, func, NULL);pthread_join(th1, NULL);pthread_join(th2, NULL);printf("s = %d\n, s);return 0;
}
改進后的代碼如下,學(xué)過操作系統(tǒng)會很好理解,無非就是為了保證共享內(nèi)存區(qū)(臨界區(qū))的原子化操作,我們可以在進這段代碼之前加鎖(pthread_mutex_lock),意味著其他線程看到這段內(nèi)存被其他人占有的時候,就不去搶占,等這段內(nèi)存被解鎖(pthread_mutex_unlock)之后,它才有讀寫這段臨界區(qū)的權(quán)利。
但其實這種方式的執(zhí)行速度并不快,比如這段代碼里,每個線程都要進行10000次加解鎖的操作,它能解決內(nèi)存讀寫沖突的問題,但是卻犧牲了效率。
鎖的作用是什么呢?
前面說過多線程是并發(fā)執(zhí)行的,th1運行后 進行了加鎖,th2這時候想要運行,就必須等待th1解鎖之后才行。
(一個衛(wèi)生間,多個人要用,第一個人進去之后,把門鎖上了,后邊的人就得排隊等著,第一個方便完了,解鎖開門出來,第二個人進去,繼續(xù)鎖門……)
鎖 在提高程序的安全性的同時,也降低了程序的效率。
鎖的使用方法
pthread_mutex_t lock; 聲明一個鎖
pthread_mutex_init(&lock,NULL); 對聲明的鎖進行初始化
pthread_mutex_lock(&lock); //上鎖 此時其他線程就開始等待
pthread_mutex_lock(&unlock); //解鎖 其他線程可以使用資源了
死鎖
拿上邊舉例,th1運行后,th2會等待th1解鎖,才能運行,如果程序出現(xiàn)錯誤中斷了,th1沒有執(zhí)行完,重新啟動后,th1又重新執(zhí)行,這時候th2排在th1前邊,需要等待th1的解鎖 ,而th1又在等待th2結(jié)束。 這樣就造成了相互等待的情況,這個就是死鎖。
互斥鎖機制
通過訪問時對共享資源加鎖的方法,防止多個線程同時訪問共享資源。鎖有兩種狀態(tài):未上鎖和已上鎖。在訪問共享資源時,進行上鎖,在訪問結(jié)束后,進行解鎖。若在訪問時,共享資源已被其它線程鎖住了,則進入堵塞狀態(tài)等待該線程釋放鎖再繼續(xù)下一步的執(zhí)行。這種鎖我們稱為互斥鎖。
互斥鎖相關(guān)函數(shù)介紹:
1、pthread_mutex_init :初始化一個互斥鎖。
函數(shù)原型:int pthread_mutex_init(pthread_mutex_tmutex,constpthread_mutexattr_tattr);
2、pthread_mutex_lock:若所訪問的資源未上鎖,則進行l(wèi)ock,否則進入堵塞狀態(tài)。
函數(shù)原型:intpthread_mutex_lock(pthread_mutex_t*mutex);
3、pthread_mutex_unlock:對互斥鎖進行解鎖。
函數(shù)原型:intpthread_mutex_unlock(pthread_mutex_t*mutex);
4、pthread_mutex_destroy:銷毀一個互斥鎖。
函數(shù)原型:intpthread_mutex_destroy(pthread_mutex_t*mutex);
生產(chǎn)者消費者模型
生產(chǎn)者和消費者在同一時間段內(nèi)共用同一個存儲空間,生產(chǎn)者往存儲空間中生成產(chǎn)品,消費者從存儲空間中取走產(chǎn)品。當(dāng)存儲空間為空時,消費者阻塞;當(dāng)存儲空間滿時,生產(chǎn)者阻塞。(下面代碼中存儲空間為1)
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>int buf = 0;pthread_mutex_t mut;
void producer(){while(1){pthread_mutex_lock(&mut);if(buf == 0){buf = 1;printf("produced an item.\n");sleep(1);}pthread_mutex_unlock(&mut);}
}
void consumer(){while(1){pthread_mutex_lock(&mut);if(buf == 1){buf = 0;printf("consumed an item.\n");sleep(1);}pthread_mutex_unlock(&mut);}
}int main(void){pthread_t thread1,thread2;pthread_mutex_init(&mut,NULL);pthread_create(&thread1,NULL,&producer,NULL);consumer(&buf);pthread_mutex_destroy(&mut);return 0;
}
從執(zhí)行結(jié)果可以看出,運行順序井然有序。生產(chǎn)后必是消費,消費完后必是生產(chǎn)。由于互斥鎖機制的存在,生產(chǎn)者和消費者不會同時對共享資源進行訪問。
信號量機制
上面了解到的互斥鎖有兩種狀態(tài):資源為0和1的狀態(tài)。當(dāng)我們所擁有的資源大于1時,可以采用信號量機制。在信號量機制中,我們有n個資源(n>0)。在訪問資源時,若n>=1,則可以訪問,同時信號量-1,否則堵塞等待直到n>=1。其實互斥鎖可以看出信號量的一種特殊情況(n=1)。
信號量相關(guān)函數(shù)的介紹:
頭文件:semaphore.h
1、sem_init函數(shù):初始化一個信號量。
函數(shù)原型:int sem_init(sem_t* sem, int pshared, unsigned int value);
參數(shù):sem:指定了要初始化的信號量的地址;pshared:如果其值為0,就表示信號量是當(dāng)前進程的局部信號量,否則信號量就可以在多個進程間共享;value:指定了信號量的初始值;返回值:成功=>0 , 失敗=> -1;2、 sem_post函數(shù):信號量的值加1,如果加1后值大于0:等待信號量的值變?yōu)榇笥?span id="ieo6y2aa" class="token number">0的進程或線程被喚醒。
函數(shù)原型:int sem_post(sem_t* sem);
返回值:成功=>0 , 失敗=> -1;3、sem_wait函數(shù):信號量的減1操作。如果當(dāng)前信號量的值大于0,則可繼續(xù)執(zhí)行。如果當(dāng)前信號量的值等于0,則會堵塞,直到信號量的值大于0.
函數(shù)原型:int sem_wait(sem_t* sem);
返回值:成功=>0 , 失敗=> -1;4、sem_destroy函數(shù):銷毀一個信號量。
函數(shù)原型:int sem_destroy(sem_t* sem);
返回值:成功=>0 , 失敗=> -1;5、sem_getvalue函數(shù):獲取信號量中的值。
函數(shù)原型:int sem_getvalue(sem_t* sem, int* sval);
獲取信號量的值,并放在&sval上。
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<unistd.h>sem_t npro; //還可以生產(chǎn)多少
sem_t ncon; //還可以消費多少void* producer(void* arg){while(1){int num;sem_wait(&npro); //先判斷是否可以生產(chǎn)sem_post(&ncon); //生產(chǎn)一個,可消費數(shù)+1sem_getvalue(&ncon,&num);printf("produce one,now have %d items.\n",num);sleep(0.7);}
}void consumer(void* arg){while(1){int num;sem_wait(&ncon); //判斷是否可以消費sem_post(&npro); //消費一個,可生產(chǎn)數(shù)+1sem_getvalue(&ncon,&num);printf("consume one,now have %d items.\n",num);sleep(1);}
}int main(void){pthread_t thread1,thread2;//init semaphoresem_init(&npro,0,5); //設(shè)最大容量為5sem_init(&ncon,0,0);pthread_create(&thread1,NULL,&producer,NULL);consumer(NULL);return 0;
}
同樣也可以解決條件競爭問題,而且使用范圍更廣了。
解決:
- pthread_join()函數(shù)
- 互斥鎖機制
- 信號量機制