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操作只是感性的認識,今天結合這個教程學習了很多,謝謝這個老師了.

視頻鏈接:https://www.bilibili.com/video/BV1Fs411M7d5?p=2


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM