Linux IPC進程間通信(三):信號量

系列文章:
Linux IPC進程間通信(一):管道
Linux IPC進程間通信(二):共享內存
Linux IPC進程間通信(三):信號量
Linux IPC進程間通信(四):消息隊列


此篇文章參考於:Linux系統編程—信號量

前言

信號量的使用方法與 共享內存,消息隊列均不同,後者是主要方便進程間通信,而信號量的作用是 對共享資源的互斥訪問
當信號量的資源數量 <= 0 時,則會阻塞,直到資源數量 >0 時纔開始進行操作,另外信號量的操作均爲原子操作。

信號量的主要兩種用法(根據信號量初始資源數量的不同, 或者說有沒有利用到信號量的資源值):

  1. 互斥鎖(信號量的初始值爲1)
    可以將信號量的資源數量設置爲1, P操作加鎖,V操作解鎖,主要用於實現對公共資源的互斥訪問

  2. 計數信號量(信號量的初始值 > 1)
    信號量的初始值大於1時,進行P操作進行消費, V操作進行生產,當信號量的資源值<=0 時阻塞等待,主要用於實現生產者消費者案例


信號量的使用

  • int semget(key_t keys, int nsems, int flags)

創建一個(或多個信號量)
keys: 用來判斷是創建新的信號量還是用已有的信號量
nsems: 信號量的數量
flags: 創建的權限及特性

  • int semop(int semid, struct sembuf* sops, size_t nops)

將要進行的操作 sop 添加到信號量semid中
semid: 信號量的標識
sops: 添加的操作結構體
nops: 添加操作結構體的個數,若添加的操作只有1個,那麼 nops=1。

struct sembuf* sops 有三個成員
sem_num : 代表着要操作的信號量的下標(從0開始)
sem_op : 要進行的操作, 一般是 1 或 -1 , 也就是 P/V操作
sem_flg : 一般爲0, 表示undo

  • int semctl(int semid, int semnum, int cmd)

對信號量semid進行操作

  • sennum 信號量集合中的下標,一般來說只有一個信號量,也就是爲 0
  • cmd 執行的命令,一般有如 GETVAL , IPC_RMID, SETVAL, SETVALL 等操作

另外,我們通常會使用 ipcs 命令來查看信號量的使用:
在這裏插入圖片描述

通過以下示例程序可以更深入理解。

1.針對一個信號量集合中只有一個信號量的情況

int test1()
{
    int ret;
    int semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
    ERROR_CHECK(semid, -1, "semget");

    semctl(semid, 0, SETVAL, 10); //設置信號量的初始值爲10
    struct sembuf sop;
    sop.sem_num = 0; //下標爲0的信號量
    sop.sem_op = 1;  //V操作
    sop.sem_flg = 0; //undo操作
    ret = semop(semid, &sop, 1);
    ERROR_CHECK(ret, -1, "semop");
    cout << semctl(semid, 0, GETVAL) << endl;
    ret = semctl(semid, 0, IPC_RMID);
    ERROR_CHECK(ret, -1, "semctl");
    return 0;
}

2.針對一個信號量集合中有多個信號量的情況

int test2()
{
    int ret;
    int semid = semget((key_t)0x1234, 3, 0666 | IPC_CREAT);
    ERROR_CHECK(semid, -1, "semget");
    unsigned short array[3] = {10, 20, 30}; //爲三個信號量設定初始值
    semctl(semid, 0, SETALL, array);

    //爲三個信號量分別 +1 +2 +3
    struct sembuf sop[3];
    sop[0].sem_num = 0;
    sop[0].sem_op = 1;
    sop[0].sem_flg = 0;

    sop[1].sem_num = 1;
    sop[1].sem_op = 2;
    sop[1].sem_flg = 0;

    sop[2].sem_num = 2;
    sop[2].sem_op = 3;
    sop[2].sem_flg = 0;

    ret = semop(semid, sop, 3);
    ERROR_CHECK(ret, -1, "semop");
    cout << "第0個信號量的值: " << semctl(semid, 0, GETVAL) << endl;
    cout << "第1個信號量的值: " << semctl(semid, 1, GETVAL) << endl;
    cout << "第2個信號量的值: " << semctl(semid, 2, GETVAL) << endl;
    semctl(semid, 0, IPC_RMID);
    return 0;
}

信號量的用法一:互斥鎖

案例:兩個進程同時加一千萬(在共享內存中),並查看最終值是否爲兩千萬

int add_and_count()
{
    int N = 10000000;
    int shmid = shmget((key_t)0x1000, 4, 0666 | IPC_CREAT);
    ERROR_CHECK(shmid, -1, "shmget");
    int *sum = (int *)shmat(shmid, NULL, 0);
    ERROR_CHECK(sum, (int *)-1, "shmat");
    *sum = 0;
    int semid = semget((key_t)0x1234, 1, 0666 | IPC_CREAT);
    ERROR_CHECK(semid, -1, "semget");
    semctl(semid, 0, SETVAL, 1);
    struct sembuf P, V;
    P = {0, -1, 0};
    V = {0, 1, 0};
    if (fork() > 0)
    {
        for (int i = 0; i < N; i++)
        {
            semop(semid, &P, 1);
            (*sum)++;
            semop(semid, &V, 1);
        }
        wait(NULL);
        cout << "final sum = " << *sum << endl;
        semctl(semid, 0, IPC_RMID);
        shmctl(shmid, IPC_RMID, NULL);
    }
    else
    {
        for (int i = 0; i < N; i++)
        {
            semop(semid, &P, 1);
            (*sum)++;
            semop(semid, &V, 1);
        }
    }

    return 0;
}

信號量的用法二 : 計數信號量

案例:生產者消費者
一個倉庫有10個空位置存放產品,生產者生產產品(沒有空位子時等待),消費者消費產品(沒有產品時等待)

int producer_consumer()
{
    int ret;
    int semid = semget((key_t)0x1234, 2, 0666 | IPC_CREAT);
    ERROR_CHECK(semid, -1, "semget");
    unsigned short array[2] = {10, 0}; //空位子,成品數
    semctl(semid, 0, SETALL, array);
    struct sembuf P1, V1, P2, V2;
    P1 = {0, -1, 0};
    P2 = {1, -1, 0};
    V1 = {0, 1, 0};
    V2 = {1, 1, 0};

    //生產者
    if (fork() > 0)
    {
        for (int i = 0; i < 5; i++)
        {
            semop(semid, &P1, 1);
            cout << "Producing..." << endl;
            semop(semid, &V2, 1);
            cout << "after producing, space = " << semctl(semid, 0, GETVAL) << " , production = " << semctl(semid, 1, GETVAL) << endl;
            sleep(2);
        }
        wait(NULL);
        semctl(semid, 0, IPC_RMID);
    }
    //消費者
    else
    {
        for (int i = 0; i < 5; i++)
        {
            semop(semid, &P2, 1);
            cout << "Consuming..." << endl;
            semop(semid, &V1, 1);
            cout << "after consuming, space = " << semctl(semid, 0, GETVAL) << " , production = " << semctl(semid, 1, GETVAL) << endl;
            sleep(1);
        }
    }
    return 0;
}

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