線程的同步與互斥

線程的同步與互斥:

  • 互斥:當一個公共資源同一時刻只能被一個進程或線程使用,多個進程或線程不能同時使用公共資源。如:當線程A在使用打印機時,其他線程都需要等待。
  • 同步:兩個或兩個以上的進程或線程在運行過程中協同步調,按預定的先後次序運行。如:A任務的運行依賴B任務產生的數據。
  • 同步保證了線程運行的順序性,互斥保證了線程運行的安全性。
  • 同步是一種更爲複雜的互斥,而互斥是一種特殊的同步。
  • 互斥具有唯一性和排它性,訪問是無序的。
  • 由於線程共享進程的資源和地址空間,所以在訪問到他們的公共資源的時候,一定會出現線程的同步和互斥現象,多線程訪問一個數據的次序一定是雜亂無章的,所以我們引入互斥鎖保證在某一端時間內只有一個線程在執行某些代碼,條件變量完成同步過程。
  • 實現線程同步互斥的四種方式
    1.臨界區:適合一個進程內的多線程訪問公共區域或代碼段使用
    2.互斥量:適合不同進程內多線程訪問公共區域或代碼段使用,與臨界區相似
    3.事件:通過線程間觸發事件實現同步互斥
    4.信號量:與臨界區和互斥量不同,可以實現多個線程同時訪問公共區域數據,原理與操作系統中的PV操作類似,先設置一個訪問公共區域的線程最大連接數,每有一個線程訪問共享區資源數就減1,直到資源數小於等於0。
    這裏寫圖片描述
    隨着現在科技的不斷髮展,許多事情都可以在網上進行,如在網上購票。
  • 現在就舉一個看電影買票的例子,一個影廳的座位數量是固定的,比如一個影廳有100個座位,那最多也只能賣出去100張票,不能存在賣出101張甚至更多的情況,如果這樣那估計這個影院馬上就要被客戶投訴了。可是每個人買票可能是在同一時間使用不同的手機和不同軟件,所以類比操作系統中多線程在運行。

下面看一段代碼:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
int ticket = 100;

void *route(void *arg)
{
    char *id = (char*)arg;
    while(1)
    {
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket : %d\n",id,ticket);
            ticket--;
        }
        else
        {
            break;
        }
    }
}
int main()
{
    pthread_t t1,t2,t3,t4;

    pthread_create(&t1,NULL,route,"thread 1");
    pthread_create(&t2,NULL,route,"thread 2");
    pthread_create(&t3,NULL,route,"thread 3");
    pthread_create(&t4,NULL,route,"thread 4");

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_join(t4,NULL);
}

看着貌似沒什麼問題,但是請看運行結果:
這裏寫圖片描述

  • 前面的結果都是正常的,但爲什麼會出現-1,-2呢??
    原因是因爲if語句判斷條件爲真以後,代碼可以併發的切換到其他線程,而usleep這個模擬漫長業務的過程,在這個漫長的業務過程中,可能會有很多個線程會進入該代碼段
  • 如果想要解決上面的問題也很簡單,做到以下四點就可以了:
    1.代碼必須要有互斥行爲,當代嗎進入臨界區執行時,不允許其他線程進入臨界區
    2.如果多個線程同時要求執行臨界區的代碼,並且臨界區沒有線程在執行,那麼只允許一個線程進入該臨界區
    3.如果線程不在臨界區執行,那麼該線程不能阻塞其他線程進入臨界區
    4.全局變量被多個線程同時訪問,要保證原子的
  • 這就要需要對互斥量進行加鎖和解鎖操作:
    下面介紹互斥量的接口
  • 互斥量的初始化
    法1:靜態分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

法2:動態分配

int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
參數:
mutex:要初始化的互斥量
attr:NULL
  • 互斥量的銷燬
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 銷燬互斥量需要注意:
    1.使用PTHREAD_MUTEX_INITALIZER初始化的互斥量不需要銷燬
    2.不要銷燬一個已經加鎖的互斥量
    3.已經銷燬的互斥量,要確保後面不會有線程再嘗試加鎖

  • 互斥量加鎖和解鎖

int pthread_mutex_lock(pthread_mutex_t *mutex);加鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex);解鎖

改進上面的代碼:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<sched.h>
int ticket = 100;
pthread_mutex_t mutex;

void *route(void *arg)
{
    char *id = (char*)arg;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if(ticket > 0)
        {
            usleep(1000);
            printf("%s sells ticket : %d\n",id,ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);
        }
        else
        {
            pthread_mutex_unlock(&mutex);
            break;
        }
    }
}
int main()
{
    pthread_t t1,t2,t3,t4;

    pthread_mutex_init(&mutex,NULL);
    pthread_create(&t1,NULL,route,"thread 1");
    pthread_create(&t2,NULL,route,"thread 2");
    pthread_create(&t3,NULL,route,"thread 3");
    pthread_create(&t4,NULL,route,"thread 4");

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);
    pthread_join(t3,NULL);
    pthread_join(t4,NULL);
}

可以看到運行結果和我們預想的一樣:
這裏寫圖片描述

爲了完成同步過程,我們引入條件變量:

  • 條件變量的初始化
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
參數:
cond:要初始化的條件變量
attr:NULL
  • 條件變量的銷燬
int pthread_cond_destroy(pthread_cond_t *cond)
  • 等待條件滿足
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
參數:
cond:要在這個條件變量上等待
mutex:互斥量


1.必須有互斥鎖與它配合使用,這是爲什麼呢??
A.條件等待是線程間同步的一種手段,如果只有一個線程,條件不滿足,永遠等下去也不會滿足,所以必須要有一個線程通過某些操作,改變共享變量,使原先不滿足的條件變得滿足,並通知等待在條件變量上的線程
B.條件不會無緣無故的突然就滿足了,必然會牽扯到共享數據的變化,所以要用互斥鎖來保護,沒有互斥鎖,就無法安全的獲取和修改共享數據
2.等的同時釋放鎖,如果不釋放就會造成死鎖問題,當signal或其它方式喚醒時要重新獲得鎖並從等待處繼續執行

  • 條件變量的使用規範
  • 等待條件
pthread_mutex_lock(&mutex);
while(條件爲假)
pthread_cond_wait(cond,mutex);
  • 修改條件
pthread_mutex_unlock(&mutex);
給條件發送信號
pthread_mutex_lock(&mutex);
設置條件爲真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);
  • 喚醒等待
int pthread_cond_broadcast(pthread_cont_t *cond);喚醒一羣
int pthread_cond_signal(pthread_cont_t *cond);喚醒單個

下面是一個簡單的程序:

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>

pthread_cond_t cond;
pthread_mutex_t mutex;

void *r1(void *arg)
{
    while(1)
    {
        pthread_cond_wait(&cond,&mutex);
        printf("hello world\n");
    }
}
void *r2(void *arg)
{
    while(1)
    {
        pthread_cond_signal(&cond);
        sleep(1);
    }
}
int main()
{
    pthread_t t1,t2;

    pthread_cond_init(&cond,NULL);
    pthread_mutex_init(&mutex,NULL);

    pthread_create(&t1,NULL,r1,NULL);
    pthread_create(&t2,NULL,r2,NULL);

    pthread_join(t1,NULL);
    pthread_join(t2,NULL);

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
}

每隔1秒就會輸出一個hello world:
這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章