Linux進程間通信五 Posix 信號量簡介與示例

1. 信號量簡介

信號量用於進程或線程間同步,Posix信號量是一個非負整型,只有兩種操作,加一(sem_post)和減一(sem_wait),如果信號量值爲0,sem_wait默認阻塞。

Posix信號量有兩種,有名信號量和無名信號量,顧名思義,就是是否有名字。有名信號量有一個名字,長度不超過NAME_MAX-4(i.e. 251),因爲內核會默認加上'sem.',所以這裏要減4,名字以斜槓開頭'/',後面跟上一個或多個非斜槓字符。不同進程可以通過同一個名字來操作有名信號量,sem_open用於創建或者獲取已存在的信號量,創建好之後就可以使用sem_post或者sem_wait來操作。使用完之後可以使用sem_close來關閉信號量,sem_unlink用來刪除信號量,刪除並不立即銷燬,只有當所有進程都sem_close纔開始銷燬。

無名信號量沒有名字,基於內存,通常用在同一個進程線程之間或者不同進程的共享內存裏,因爲同一個進程的不同線程共享進程地址空間,所以可以訪問到,放到共享內存裏也可以被不同進程訪問到。使用前必須使用sem_init進行初始化,初始化之後就可以使用sem_waitsem_post操作,使用完成後sem_destroy接口進行銷燬,下一節介紹接口的使用

2. 信號量API接口

2.1 有名信號量創建

#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>

/**
* @brief 創建或獲取有名信號量
*
* @params name 有名信號量關聯的名字,斜槓開頭,長度不超過NAME_MAX-4(e.g. 251)
* @params oflag 標誌位,可選值包括O_CREAT| O_EXCL
* 這裏如果指定了O_CREAT標誌位,還要填寫額外兩個參數,mode和value
*
* @params mode,參考open函數,通常填0即可
* @params value 信號量的初始值
* @returns 成功返回描述符,失敗返回-1
**/

sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
                       mode_t mode, unsigned int value);

# 編譯加上 -pthread選項
Link with -pthread.

2.2 信號量減一操作

#include <semaphore.h>

/**
* @brief lock信號量並減1,當信號量大於0,操作可以執行,否則則阻塞。如果設置了NONBLOCK標誌位,則報錯返回
*
* @params sem 信號量描述符
* @returns 成功返回0,失敗返回-1
**/
int sem_wait(sem_t *sem);

/**
* @brief 同sem_wait,只不過如果無法減1則立即報錯返回
*
**/
int sem_trywait(sem_t *sem);

/**
* @brief 同sem_wait,只不過如果無法減1則會等待一段時間,注意這裏時間參數要設置正確
*
**/
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

# 編譯鏈接選項 -pthread
Link with -pthread.

2.3 信號量加1操作

#include <semaphore.h>

/**
* @brief 信號量的值加1
*
* @params sem 信號量文件描述符
* @returns 成功返回0,失敗返回-1
**/

int sem_post(sem_t *sem);

# 編譯鏈接選項 -pthread
Link with -pthread.

2.4 關閉有名信號量

#include <semaphore.h>

/**
* @brief 關閉信號量,系統爲當前進程分配的信號量資源會被釋放。
*
* @params sem 信號量文件描述符
* @returns 成功返回0,失敗返回-1
**/

int sem_close(sem_t *sem);

# 編譯鏈接選項 -pthread
Link with -pthread.

2.5 銷燬有名信號量

#include <semaphore.h>

/**
* @brief 銷燬信號量,只有當所有進程都關閉信號量之後纔開始銷燬工作
*
* @params name 信號量名字
* @returns 成功返回0,失敗返回-1
**/

int sem_unlink(const char *name);

# 編譯鏈接選項 -pthread
Link with -pthread.

2.6 初始化無名信號量

#include <semaphore.h>

/**
* @brief 初始化無名信號量
*
* @params sem 待初始化的信號量地址
* @params pshared 爲0表示線程間共享,非0表示進程間共享
* @params value 信號量初始值,不超過SEM_VALUE_MAX
* @returns 成功返回0,失敗返回-1
**/

int sem_init(sem_t *sem, int pshared, unsigned int value);

# 編譯鏈接選項 -pthread
Link with -pthread.

2.7 銷燬無名信號量

#include <semaphore.h>

/**
* @brief 銷燬無名信號量
*
* @params sem 要銷燬的信號量
* 如果有其它線程或進程阻塞在sem上,此時銷燬會產生未定義的行爲
*
* @returns 成功返回0,失敗返回-1
**/

int sem_destroy(sem_t *sem);

# 編譯鏈接選項 -pthread
Link with -pthread.

3 有名信號量例子

3.1 信號量生產者


// 生產者
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#define SEM_NAME "/sem0"

int main(int argc, char** argv)
{
    if (argc < 3)
    {
        printf("Usage: ./sem_post timeval nums\n");
        return -1;
    }

    int ts = atoi(argv[1]);
    int total = atoi(argv[2]);
    if (total < 1 || ts < 1)
    {
        printf("invalid param\n");
        return -1;
    }

    sem_t* sem_id;
    // 創建信號量並初始化值爲0
    sem_id = sem_open(SEM_NAME, O_CREAT, O_RDWR, 0);
    if (sem_id == SEM_FAILED)
    {
        perror("sem_open error");
        return -1;
    }

    int curr = 0;
    while (curr < total)
    {        
        // 生成信號量,即加1
        while (sem_post(sem_id))
        {
            perror("sem_post error, try later");
            sleep(1);
        }
        printf("producing succ\n");
        sleep(ts);
        ++curr;
    }
    printf("work done\n");

    // 關閉信號量
    sem_close(sem_id);
    return 0;
}

3.2 信號量消費者


// 消費者
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <semaphore.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>

#define SEM_NAME "/sem0"

int main()
{
    sem_t* sem_id;

    // 創建信號量並初始化值爲0
    sem_id = sem_open(SEM_NAME, O_CREAT, O_RDWR, 0);
    if (sem_id == SEM_FAILED)
    {
        perror("sem_open error");
        return -1;
    }

    while (1)
    {
        // 消費信號量
        if (sem_wait(sem_id))
        {
            perror("sem_wait fail, try later\n");
            sleep(1);
            continue;
        }
        printf("consuming succ\n");
    }

    // 關閉信號量
    sem_close(sem_id);
    return 0;
}

3.3 編譯&運行

default:
	gcc -o sem_post sem_post.c -pthread
	gcc -o sem_wait sem_wait.c -pthread
clean:
	rm -rf sem_wait sem_post

 

4. 無名信號量例子

創建兩個線程,分別用於生產和消費

#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

// 消費者
void* consumer_worker(void *arg)
{
    sem_t *sem = arg;
    while (1)
    {
        // 消費信號量
        if (sem_wait(sem))
        {
            perror("[consumer] sem_wait error, try later\n");
            sleep(1);
            continue;
        }
        printf("[consumer] consume succ\n");
    }
   return 0;
}


// 生產者
void* producer_worker(void *arg)
{
    sem_t *sem = arg;
    while (1)
    {
        // 生成信號量
        if (sem_post(sem))
        {
            perror("[producer] sem_post error, try later\n");
            sleep(1);
            continue;
        }
        printf("[producer] produce succ\n");
        sleep(1);
    }
   return 0;
}

int main(int argc, char** argv)
{
    pthread_t consumer, producer;
    if (argc < 2)
    {
        printf("Usage: ./unnamed_sem time\n");
        return -1;
    }

    int tm = atoi(argv[1]);
    if (tm < 1)
    {
        printf("invalid param\n");
        return -1;
    }
    
    sem_t sem;
    // 無名信號量初始化
    if (sem_init(&sem, 0, 0))
    {
        perror("sem_init error");
        return -1;
    }

    // 創建生產者線程
    if (pthread_create(&producer, NULL, &producer_worker, (void *)&sem))
    {
        perror("create producer_worker error");
        sem_destroy(&sem);
        return -1;
    }

    // 創建消費者線程
    if (pthread_create(&consumer, NULL, &consumer_worker, (void *)&sem))
    {
        perror("create consumer_worker error");
        sem_destroy(&sem);
        return -1;
    }

    printf("main thread sleep:%d\n", tm);
    sleep(tm);

    // 無名信號量銷燬
    sem_destroy(&sem);
    return 0;
}

5. 參考資料

1. https://www.man7.org/linux/man-pages/man7/sem_overview.7.html

2. 《Linux環境編程 從應用到內核》 

================================================================================================

Linux應用程序、內核、驅動、後臺開發交流討論羣(745510310),感興趣的同學可以加羣討論、交流、資料查找等,前進的道路上,你不是一個人奧^_^。

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