天天看點

線程同步——互斥量互斥鎖原理模拟生産者消費者模型為模型加鎖

互斥鎖原理

生産者消費者模型裡,有兩個線程,生産者程序和消費者程序。這兩個線程最壞的情況下會同時操作臨界資源會引起線程同步的問題。

線程同步——互斥量互斥鎖原理模拟生産者消費者模型為模型加鎖

互斥量如何解決這一問題?

如上圖,當線程1在操作臨界資源的時候,它可以阻止另外一個線程通路臨界資源。

在生産者與消費者模型中,引發線程同步的根本原因在于兩個線程的指令可能是交叉執行的,每一個線程有3條指令,它可能先執行線程1的1條指令,在執行線程2的兩條指令後接着執行線程1的指令,如此交叉往複。使用互斥量就會讓兩個線程的指令先後執行。依舊是生産者消費者模型,互斥量可能先讓線程1執行完所有指令再執行線程2,也可能讓線程2執行完所有指令再執行線程1。

這一效果也稱為原子性,互斥量其實就是保證了關鍵指令的原子性。

原子性指的是一系列的操作

不可被中斷

的特性,這一系列的操作要麼全部執行完成,要麼全部沒有執行,不存在部分執行部分沒有執行的情況。

互斥量是線程同步的最簡單的方式

,也把它成為互斥鎖,它是處于兩個狀态之一的變量:要麼是解鎖狀态,要麼是加鎖狀态。這兩個狀态可以保證資源通路的串行。如果臨界資源被某一線程所使用,另一個線程需要使用資源的話,隻能等待正在使用資源的線程釋放掉,解鎖之後才能使用資源。

作業系統提供了互斥量API,可以直接使用API完成資源的加鎖,解鎖操作。

pthread_mutex_t

模拟生産者消費者模型

#include <stdio.h>
#include <pthread.h>
#include <vector>

// 臨界資源
int num = 0;

// 生産者
void *producer(void *) {
    int times = 1000000;
    while (times--)
        num += 1;

}

// 消費者
void *comsumer(void *) {
    int times = 1000000;
    while (times--)
        num -= 1;
}

int main() {

    for (int i = 0; i < 10; ++i) {
        pthread_t thread1, thread2;
        pthread_create(&thread1, NULL, &producer, NULL);
        pthread_create(&thread2, NULL, &comsumer, NULL);
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        printf("臨界資源:%d\n", num);
    }

    return 0;
}

           

上述代碼模拟生産者消費者模型,每次都對臨界資源進行100萬次操作,列印10次這樣的結果如下。

臨界資源:-950191
臨界資源:-1632377
臨界資源:-674487
臨界資源:-413055
臨界資源:-1384356
臨界資源:-470107
臨界資源:406732
臨界資源:-590458
臨界資源:-729773
臨界資源:-353279
           

觀察發現生産者消費者循環的次數都是一樣的,此時的臨界資源應該還是初始值0,但是結果卻不是這樣的。

為模型加鎖

使用互斥鎖改進代碼,在生産者與消費者循環時進行加鎖和解鎖

#include <stdio.h>
#include <pthread.h>
#include <vector>

// 使用互斥量解決線程同步問題 初始化互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 臨界資源
int num = 0;

// 生産者
void *producer(void *) {
    int times = 1000000;
    while (times--) {
        pthread_mutex_lock(&mutex);
        num += 1;
        pthread_mutex_unlock(&mutex);
    }
}

// 消費者
void *comsumer(void *) {
    int times = 1000000;
    while (times--) {
        pthread_mutex_lock(&mutex);
        num -= 1;
        pthread_mutex_unlock(&mutex);
    }
}

int main() {

    for (int i = 0; i < 10; ++i) {
        pthread_t thread1, thread2;
        pthread_create(&thread1, NULL, &producer, NULL);
        pthread_create(&thread2, NULL, &comsumer, NULL);
        pthread_join(thread1, NULL);
        pthread_join(thread2, NULL);
        printf("臨界資源:%d\n", num);
    }

    return 0;
}
           

輸出結果如下:

臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
臨界資源:0
           

加鎖之後每一次都帶來了性能損耗,是以執行時間可能會變長。

總結互斥量

如果有多個線程同時通路臨界資源,互斥量保證臨界資源是被串行通路的。某個線程通路臨界資源,線程首先給臨界資源加鎖,加鎖好其他資源無法通路,其他線程隻能等目前線程解鎖後才能使用。就比如有一堆人上廁所,但廁所隻有一個,一個人進去之後需要把門反鎖上,這樣他上廁所時候就不會有人開門,上完廁所後把門解鎖,後面的人繼續排隊上廁所。

繼續閱讀