【linux】linux進程間通訊--信號量

(一)信號量的產生

      信號量是由 Dijkstra 提出來的,信號量概念是由併發編程領域邁出的重要一步。信號量的產生是爲了在多個進程同時訪問系統上某個資源時(同時寫一個數據庫的某條記錄、同時修改某個文件),以確保同一時刻只有一個進程可以擁有對資源的獨佔。

(二)關鍵代碼段(臨界區)

    通常,程序對共享資源訪問的代碼知識短短的一部分,,但就是這短短的幾句代碼引發了進程之間的競態條件。我們稱這段代碼爲關鍵代碼段或者臨界區。

(三)信號量的工作原理

    信號量是一種特殊的變量,它只能取得自然數值並且只支持兩種操作(pv操作); 信號量可以取任何自然數,但是最常用的,最簡單的信號量還是二進制信號量(二進制信號量只能取0和1這兩個值)。
    假設信號量sv(初始值可以當做資源數),對他的pv操作如下:

p(sv):如果sv的值大於0,就將它減去一;如果sv的值爲0,則將掛起進程的執行(阻塞);
v(sv):如果有其他進程因爲等待sv而掛起,則喚醒之,如果沒有,則將sv加一;

   或許這讓說還是不太清楚,那麼下面我再通過一個簡單地例子來說明信號量的工作原理。

   假設一家銀行有三個辦理窗口,但是此時需要辦理的人有5個,那麼其工作原理如下圖所示:

   

 

 

   

  

     

       其中,佔用資源被稱爲p操作,釋放資源被稱爲v操作;

   (四)相關函數介紹

        Linux提供了一組信號量接口來對信號進行操作,他們不只是針對二進制信號量,下面對這些函數進行詳細介紹(這些函數都是用來對成組的信號量進行操作的,在頭文件sys/sem.h中聲明)

       1:semget函數 【創建一個新的信號量或者取得一個已經存在的限號量】

int semget(key_t key, int num_sems, int sem_flags);

       key參數是一個鍵值(唯一非零的整數),用來標識一個全局唯一的信號量集,就像文件名全局唯一的標識一個文件一樣。通過信號量的進程需要使用相同的鍵值來創建/獲取該信號;通俗的來說就是一個鍵值,就代表某個資源,程序對所有信號量的訪問都是間接地,程序首先調用semget函數提供一個鍵,semget函數就會返回一個由系統生成的標識符,只有semget函數才能直接使用信號量鍵,其他的函數都是依靠這個鍵來完成自己的工作的。

       num_sums參=參數指=指定需要創建/獲取的信號量集中的信號量的數目。如果是創建信號量,則該值必須被指定;但如果是獲取已經存在的信號領,則可以把它設置爲0.

       sem_flags參數指定一組標誌。如果是創建一個不存在的新信號量時,可以和IPC_CREAT標誌做按位“或”運算,如果此時信號量已經存在,semget也不會出錯;還可以用IPC_CREAT | IPC_EXCL來創建一個新的,唯一的信號量,如果信號量已經存在,semget會返回一個errno爲EEXIST。

      semget函數成功時返回一個正整數,他是信號集的標識符;失敗返回-1,並設置errno‘

’      2:semop函數【用來改變信號量的值】

int semop(int sem_id, struct sembuf* sem_ops, size_t num_size_ops);

       sem_id參數是由semget函數調用返回的信號量集標識符,用以指定被操作的目標信號量集。

       sem_ops參數指向一個sembuf結構體類型的數組,sembuf的定義如下所示:

struct sembuf
{
   unsigned short int sem_num;//除非使用一組信號量,否則值爲0
   short int sem_op;//信號量在一次操作中需要改變的數據,通常爲兩個數,一個爲“-1”即p操作,一個爲“+1”即v操作
   short int sem_flg;//通常被設置爲SEM_UNDO,令操作系統跟蹤信號,並在進程沒有釋放該信號量而終止時,操作系統釋放信號量
}

        其中sem_num成員是信號量集中信號量的編號,0表示信號量集中第一個信號量。sem_op成員指定操作類型,可選值爲正整數、0、負整數。每種類型的操作的行爲又受到sem_flag成員的影響。sem_flag的可選值爲IPC_NOWAIT和SEM_UNDO。IPC_NOWAIT的含義是:無論信號量操作是否成功,semop調用都將立即返回,這類似於I/O操作。SEM_UNDO的含義是:當進程退出時,取消正在進行的semop操作。想要了解sem_op和sem_flag的具體行爲,請參照《linux高性能服務器編程》第246頁。

        3:semctl函數【直接控制信號量信息】

int semctl(int sem_id, int sem_num, int command, .......);

      前兩個參數和前面的函數中的參數意義相同,command參數指定要執行的命令。有的命令需要調用者傳遞第四個參數,第四個參數的類型是由用戶自己定義。通常command會被設置成SETVAL(用來把信號量初始化成一個已知的值。p這個值通過union semun中的val成員設置,作用是在信號領第一次使用前對它進行設置)、 IPC_RMID(刪除一個已經不需要的信號量標識符);在sys/sem.h頭文件給出了它的推薦各式:

union semun
{
    int val;
    struct semid_ds* buf;
    unsigned short* array;
    struct seminfo* _buf;
}

(五)代碼演示

     代碼中一個線程代替一個來辦理業務的顧客,用代碼來模擬5個人辦理業務的過程;

   1:不使用信號量控制多線程演示

#include <sys/sem.h>
#include <pthread.h>
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;

void *doBank(void* arg)
{
    int tmp = *(int*)arg;
    cout << "pthread" << tmp <<"start" << endl << endl;//代表顧客開始辦理業務
    for(int i = 0; i < 3; i++)
    {
        cout << "pthread"<< tmp <<"doing......" << endl << endl;//代表顧客正在辦理業務
        sleep(rand()%3);
    }
    cout << "pthread" << tmp << " end" << endl << endl;//代表顧客因爲辦理結束
}

int main()
{
    pthread_t t[6];
    void *pthread_result = NULL;
    
    srand((unsigned long)time(NULL));
    int i;
    int count[6] = {1, 2, 3, 4, 5, 6};
    for(i = 0; i < 6; i++)
    {
        int res = pthread_create(&t[i], NULL, doBank, (void*)&count[i]);
        if(res != 0)
        {
            cout << "pthread create faild." << endl;
            return 0;
        }
    }

    for(i = 0; i < 6; i++)
    {
        pthread_join(t[i], &pthread_result);
    }

    return 0;
}

   運行結果:

     

    

     從程序運行結果來看,沒有使用信號量控制資源時,每個人都在競爭業務辦理窗口,就像沒有排隊的人在爭搶窗口辦理業務,但是我們知道,在辦理業務時是不能中斷的,通俗的說,一個人只有不辦業務或辦完業務兩種狀態,不能讓一個人的業務還沒有處理完,就開始處理第二個人的業務;

   2:使用信號量控制多線程結果演示

#include <sys/sem.h>
#include <pthread.h>
#include <iostream>
#include <time.h>
#include <stdlib.h>
using namespace std;

static int sem_id = 0;

union semun
{
    int val;
};

static int sem_init()
{
    union semun sem_union;
    sem_union.val = 3;

    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
    {
        cout << "semctl faild!" << endl;
        return 0;
    }
}

static void sem_destroy()
{
    union semun sem_union;

    if(semctl(sem_id, 0, SETVAL, sem_union) == -1)
    {
        cout << "semaphore destroy faild!" << endl;
    }
    cout << "semaphore destroy successful!" << endl;
}

static int sem_p()
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = -1;
    buf.sem_flg = SEM_UNDO;
    if(semop(sem_id, &buf, 1) == -1)
    {
        cout << "semaphore_p faild!" << endl;
        return 0;
    }
    return 1;
}

static int sem_v()
{
    struct sembuf buf;
    buf.sem_num = 0;
    buf.sem_op = +1;
    buf.sem_flg = SEM_UNDO;
    if(semop(sem_id, &buf, 1) == -1)
    {
        cout << "semaphore_p faild!" << endl;
        return 0;
    }
    return 1;
}

void *doBank(void* arg)
{
    int tmp = *(int*)arg;
    
    cout << "pthread" << tmp <<"wait" << endl << endl;
    sem_p();

    cout << "pthread" << tmp <<"start" << endl << endl;
    for(int i = 0; i < 1; i++)
    {
        cout << "pthread"<< tmp <<"doing......" << endl << endl;;
        sleep(rand()%3 + 1);
    }
    cout << "pthread" << tmp << " end" << endl << endl;

    sem_v();
}

int main()
{
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
    if(sem_id == -1)
    {
        cout << "semget faild!" << endl;
    }
    else
    {
        cout << "semget successful" << endl;
    }
    

    sem_init();

    pthread_t t[6];
    void *pthread_result = NULL;
    
    srand((unsigned long)time(NULL));
    int i;
    int count[6] = {1, 2, 3, 4, 5, 6};
    for(i = 0; i < 6; i++)
    {
        int res = pthread_create(&t[i], NULL, doBank, (void*)&count[i]);
        if(res != 0)
        {
            cout << "pthread create faild." << endl;
            return 0;
        }
    }

    for(i = 0; i < 6; i++)
    {
        pthread_join(t[i], &pthread_result);
    }
    
    sem_destroy();

    return 0;
}

   

  

   從程序運行結果來看,使用信號量控制資源分配時,每個線程就像顧客排隊一樣,依次辦理業務;保證沒人每次佔用獨佔一個窗口;

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