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),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。

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