1. 程式人生 > >【Linux多執行緒】同步與互斥的區別

【Linux多執行緒】同步與互斥的區別

同步與互斥這兩個概念經常被混淆,所以在這裡說一下它們的區別。

一、同步與互斥的區別

1. 同步

同步,又稱直接制約關係,是指多個執行緒(或程序)為了合作完成任務,必須嚴格按照規定的 某種先後次序來執行。

例如,執行緒 T2 中的語句 y 要使用執行緒 T1 中的語句 x 的執行結果,所以只有當語句 x 執行完成之後語句 y 才可以執行。我們可以使用訊號量進行同步:

semaphore S=0;   // 初始化訊號量

T1() {
    ...
    x;           // 語句x
    V(S);        // 告訴執行緒T2,語句x已經完成
    ...
}

T2() {
    ...
P(S); // 檢查語句x是否執行完成 y; // 檢查無誤,執行y語句 ... }

2. 互斥

互斥,又稱間接制約關係,是指系統中的某些共享資源,一次只允許一個執行緒訪問。當一個執行緒正在訪問該臨界資源時,其它執行緒必須等待。

例如,印表機就是一種臨界資源,而訪問印表機的程式碼片段稱為臨界區,故每次只允許一個執行緒進入臨界區。—— 我們同樣可以使用訊號量解決互斥問題,只需把臨界區置於 P(S) 和 V(S) 之間,即可實現兩執行緒對臨界資源的互斥訪問。

semaphore S=1;   // 初始化訊號量

T1() {
    ...
P(S); 執行緒T1的臨界區; // 訪問臨界資源 V(S); ... } T2() { ... P(S); 執行緒T2的臨界區; // 訪問臨界資源 V(S); ... }

二、一個同步的例子

如下圖,為了求出 1 到 n 的平均值,需要三個執行緒協調它們的工作次序來完成,這就是同步:



為了使多個執行緒按順序正確執行,應設定若干個初始值為 0 的訊號量:

#include<iostream>
#include<pthread.h>
#include"semaphore.hpp"
using namespace std; int sem1, sem2; int n = 10; /*1...n的平均值*/ int sum = 0; double average = 0; void* t1(void* arg) { for(int i=1; i<=n; ++i) sum += i; sem_v(sem1); /*V操作,通知t2求和已完成*/ } void* t2(void* arg) { sem_p(sem1); /*P操作,等待t1完成*/ average = (double)sum/n; sem_v(sem2); /*V操作,通知main求平均已完成*/ } int main() { sem1 = creat_sem("/" , 0); /*建立訊號量*/ sem2 = creat_sem("/home", 0); pthread_t id[2]; pthread_create(&id[0], NULL, t1, NULL); pthread_create(&id[1], NULL, t2, NULL); sem_p(sem2); /*P操作,等待t2完成*/ cout << "The sum is: " << sum << endl; cout << "The average is: " << average << endl; del_sem(sem1); /*刪除訊號量*/ del_sem(sem2); return 0; }

下面是訊號量的相關函式,詳見《訊號量》。

// semaphore.hpp
#include<cstdio>
#include<cstdlib>
#include<sys/sem.h>

// 聯合體,用於semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化訊號量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作:
//  若訊號量值為1,獲取資源並將訊號量值-1 
//  若訊號量值為0,程序掛起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序號*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  釋放資源並將訊號量值+1
//  如果有程序正在掛起等待,則喚醒它們
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序號*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 刪除訊號量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}


// 建立訊號量,返回其ID
int creat_sem(const char* path, int value)
{
    int sem_id;  /*訊號量集ID*/
    key_t key;
    /*獲取key值*/
    if((key = ftok(path, 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    /*建立訊號量集,其中只有一個訊號量*/
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    init_sem(sem_id, value);
    return sem_id;
}

執行緒 t2 需要等待執行緒 t1 (求和)完成以後才能夠執行;主執行緒 main 需要等待執行緒 t2 (求平均)完成以後才能夠執行輸出。編譯執行結果如下:

$ g++ -lpthread -o synchronized synchronized.cpp 
$ ./synchronized 
The sum is: 55
The average is: 5.5

總結:

  1. 互斥是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。
  2. 同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。
  3. 同步其實已經實現了互斥,所以同步是一種更為複雜的互斥。
  4. 互斥是一種特殊的同步。