Linux进程间通信五 Posix 信号量简介与示例

    技术2025-05-18  52

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

    Processed: 0.009, SQL: 9