linux下的訊號量PV操作進階之路
一.同步和互斥機制
訊號量
互斥鎖
同步:指多個任務按照約定的先後次序相互配合來完成一件事情. 比如讀執行緒等待寫執行緒寫完之後再去讀.
二.訊號量-P/V操作
P(s)含義:
if(訊號量>0)
{
申請資源的任務運行;
訊號量--;
}
else
{申請資源的任務阻塞}
V(S)含義:
訊號量++;
if(有任務在等待資源)
{
喚醒等待的任務,讓其繼續運行.
}
三.Posix訊號量:
無名訊號量(基於記憶體的訊號量):多用於同一進程的多個執行緒之間.
有名訊號量:既可用於執行緒之間,也可用於進程之間.
四.無名訊號量常用函數:
#include<semaphore>
int sem_init(sem_t *sem, int pshared, unsigned int val); 初始化
pshared: 0-執行緒間 1-進程間
val:訊號量初值.
int sem_wait(sem_t *sem); P操作(可以這樣理解,等下,我看看有沒有資源)
int sem_post(sem_t *sem); V操作
五.結合例子講解
題目: 兩個執行緒同步讀寫緩衝區(生產者/消費者問題,這裡的讀寫是相對於緩衝區來說的)
否定之否定的實現道路:
1.錯誤實現1(自己一開始按照互斥思想的錯誤實現)
在同一個執行緒中開始位置寫上了sem_wait(&s),結束位置寫上了sem_post(&s).這根本就不是訊號量的用法,哈哈哈.
2.錯誤實現2(把P操作放錯位置)
#include<stdio.h> #include<pthread.h> #include<semaphore.h> char buff[100]; sem_t s; //用兩個子執行緒來實現 void *ReadTask(void *arg) { while(1) { if(strcmp(buff, "quit", 4) == 0) { printf("hehe, 遇到quit,退出\n"); break; } else { sem_wait(&s); printf("所讀內容:%s\n", buff); //把數據從快取區中讀出 } } pthread_exit("ReadTask End!"); } void *WriteTask(void *arg) { do { scanf("%s", &buff); //把數據寫到快取區中 sem_post(&s); }while(strcmp(buff, "quit", 4) != 0); pthread_exit("WriteTask End!"); } int main() {
if (sem_init(&s, 0, 0) < 0) //此處一開始將可用資源設為0.
{
perror("sem_init");
exit(-1); //直接退出執行緒
}
pthread_t t_read; pthread_t t_write; //即使這裡先創建的t_read,也不代表t_read先執行.它們的執行順序不固定的. int rc1 = pthread_create(&t_read, NULL, ReadTask, NULL);
if(rc1<0)
{
perror("pthread_create t_read");
exit(-1);
} int rc2 = pthread_create(&t_write, NULL, WriteTask, NULL);
if(rc2<0)
{
perror("pthread_create t_write");
exit(-1);
}
pthread_join(t_read, NULL); pthread_join(t_write, NULL); return 0; }
這裡本來預想的結果是:當我輸入"quit"後,直接執行if分支中的並退出執行緒.最後發現不是這個回事.發現還會進入到else的分支里,感覺很是費解.
後來gdb調試,發現問題出現在了sem_wait這一行.
當我敲入"quit"之前,讀執行緒就已經執行到了sem_wait這裡(因為buff符合else條件),只是因為訊號量為0,所以阻塞在這裡.等我敲入"quit"後,寫執行緒將其寫到buff中,然後喚醒了讀執行緒,才有了下面的結果.
quit
所讀內容:quit
hehe, 遇到quit,退出
3.對上面進行修改:
只需把sem_wait放在執行緒循環一開始的地方.
void *ReadTask(void *arg) { while(1) { sem_wait(&s); if(strcmp(buff, "quit", 4) == 0) { printf("hehe, 遇到quit,退出\n"); break; } else { printf("所讀內容:%s\n", buff); //把數據從快取區中讀出 } } pthread_exit("ReadTask End!"); }
4.用主執行緒和子執行緒來實現:
當輸入"quit"時,主執行緒循環結束,主執行緒return 0,子執行緒也跟著結束了.
為何要先初始化訊號量,再創建執行緒呢?
如果先創建執行緒的話,主執行緒和子執行緒誰先運行是不確定的,這帶來什麼影響呢? 如果把訊號量初始化放在主執行緒中並且在子執行緒創建之後,如果子執行緒中內容先執行並且對訊號量進行了操作,就會出現問題,因為此時訊號量還沒有初始化.
#include<stdio.h> #include<pthread.h> #include<semaphore.h> char buff[100]; sem_t s; //用一個子執行緒 void *ReadTask(void *arg) { while(1) { sem_wait(&s); printf("所讀內容:%s\n", buff); //把數據從快取區中讀出 } pthread_exit("ReadTask End!"); } int main() { if(sem_init(&s, 0, 0)) //此處一開始將可用資源設為0.
{
perror("sem_init");
exit(-1);
}
pthread_t t_read; //即使這裡先創建的t_read,也不代表t_read先執行.它們的執行順序不固定的. int rc1 = pthread_create(&t_read, NULL, ReadTask, NULL); if(rc1 < 0)
{
perror("pthread_create t_read");
exit(-1);
}
do { scanf("%s", &buff); //把數據寫到快取區中 sem_post(&s); }while(strcmp(buff, "quit", 4) != 0); return 0; }
5.以上幾種情況使用一個訊號量存在的問題,不是嚴格意義上的同步(嚴格意義上:寫的時候,不要讀;讀的時候不要寫):
上面例子中的訊號量實際上是針對讀執行緒的,保證緩衝區中有數據時才可以去讀.但是並沒有針對寫執行緒,因為需要保證在讀的時候,不要往緩衝區中去寫入.不然會造成讀取異常的問題.
舉例:
上述的例子中,如果讀執行緒花的時間比較長,而寫執行緒一直往裡面寫.就會導致前面的內容被後面的給覆蓋掉,而讀執行緒只是讀到了後面的內容.
void *ReadTask(void *arg) { while(1) { sem_wait(&s); sleep(5); //來模擬讀的過程時間長 printf("所讀內容:%s\n", buff); //把數據從快取區中讀出 } pthread_exit("ReadTask End!"); }
可能的結果是
aaa
bbb
ccc
所讀內容:ccc
所讀內容:ccc
所讀內容:ccc
quit
6.用兩個訊號量來實現同步過程:

#include<stdio.h> #include<pthread.h> #include<semaphore.h> char buff[100]; sem_t sem_r; sem_t sem_w; //用一個子執行緒 void *ReadTask(void *arg) { while(1) { sem_wait(&sem_r); //判斷sem_r是否大於0,是的話,就去--,申請資源的任務運行;否的話就阻塞 sleep(2); printf("所讀內容:%s\n", buff); //把數據從快取區中讀出 sem_post(&sem_w);//讀的訊號量++,如果有讀操作處於阻塞,就把它給喚醒. } pthread_exit("ReadTask End!"); } int main() { if(sem_init(&sem_w, 0, 1)<0) //寫訊號量一開始為1 { perror("sem_init sem_w"); exit(-1); } if(sem_init(&sem_r, 0, 0)<0) //讀訊號量一開始為0 { perror("sem_init sem_r"); exit(-1); } pthread_t t_read; //即使這裡先創建的t_read,也不代表t_read先執行.它們的執行順序不固定的. if(pthread_create(&t_read, NULL, ReadTask, NULL)<0) { error("pthread_create t_read"); exit(-1); } do { sem_wait(&sem_w); scanf("%s", &buff); //把數據寫到快取區中 sem_post(&sem_r); }while(strcmp(buff, "quit", 4) != 0);
return 0; }
運行結果如下: 這個時候來不及處理的字元串會阻塞在sem_wait(&sem_w);它沒有進到緩衝區中會在輸入窗中排隊.
a
b
c
d
所讀內容:a
所讀內容:b
所讀內容:c
所讀內容:d
之前對PV操作只是感性的認識,今天結合這個教程學習了很多,謝謝這個老師了.
影片鏈接://www.bilibili.com/video/BV1Fs411M7d5?p=2

