一.同步和互斥機制
信號量
互斥鎖
同步:指多個任務按照約定的先后次序相互配合來完成一件事情. 比如讀線程等待寫線程寫完之后再去讀.
二.信號量-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操作只是感性的認識,今天結合這個教程學習了很多,謝謝這個老師了.