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. sem_overview(7) - Linux manual page

2. 《Linux环境编程 从应用到内核》 

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础() 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程() read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现 网络编程, Linux
Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础() 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户/服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程() read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信号量 信号量集结构 信号量集函数 信号量示例 31System V信号量(二) 用信号量实现进程互斥示例 32System V信号量(三) 用信号集解决哲学家就餐问题 33System V共享内存与信号量综合 用信号量解决生产者消费者问题 实现shmfifo 34POSIX消息队列 POSIX消息队列相关函数 POSIX消息队列示例 35POSIX共享内存 POSIX共享内存相关函数 POSIX共享内存示例 Linux网络编程之线程篇 36线程介绍 什么是线程 进程与线程 线程优缺点 线程模型 N:1用户线程模型 1:1核心线程模型 N:M混合线程模型 37POSIX线程(一) POSIX线程库相关函数 用线程实现回射客户/服务器 38POSIX线程(二) 线程属性 线程特定数据 39POSIX信号量与互斥锁 POSIX信号量相关函数 POSIX互斥锁相关函数 生产者消费者问题 自旋锁与读写锁介绍 40POSIX条件变量 条件变量 条件变量函数 条件变量使用规范 使用条件变量解决生产者消费者问题 41一个简单的线程池实现 线程池性能分析 线程池实现
Linux网络编程(总共41集) 讲解Linux网络编程知识,分以下四个篇章。 Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念(对等通信、封装、分用、端口) 02TCPIP基础(二) 最大传输单元(MTU)/路径MTU 以太网帧格式 ICMP ARP RARP 03TCPIP基础(三) IP数据报格式 网际校验和 路由 04TCPIP基础(四) TCP特点 TCP报文格式 连接建立三次握手 连接终止四次握手 TCP如何保证可靠性 05TCPIP基础() 滑动窗口协议 UDP特点 UDP报文格式 Linux网络编程之socket编程篇 06socket编程(一) 什么是socket IPv4套接口地址结构 网络字节序 字节序转换函数 地址转换函数 套接字类型 07socket编程(二) TCP客户/服务器模型 回射客户 /服务器 socket、bind、listen、accept、connect 08socket编程(三) SO_REUSEADDR 处理多客户连接(process-per-conection) 点对点聊天程序实现 09socket编程(四) 流协议与粘包 粘包产生的原因 粘包处理方案 readn writen 回射客户/服务器 10socket编程() read、write与recv、send readline实现 用readline实现回射客户/服务器 getsockname、getpeername gethostname、gethostbyname、gethostbyaddr 11socket编程(六) TCP回射客户/服务器 TCP是个流协议 僵进程与SIGCHLD信号 12socket编程(七) TCP 11种状态 连接建立三次握手、连接终止四次握手 TIME_WAIT与SO_REUSEADDR SIGPIPE 13socket编程(八) 种I/O模型 select 用select改进回射客户端程序 14socket编程(九) select 读、写、异常事件发生条件 用select改进回射服务器程序。 15socket编程(十) 用select改进第八章点对点聊天程序 16socket编程(十一) 套接字I/O超时设置方法 用select实现超时 read_timeout函数封装 write_timeout函数封装 accept_timeout函数封装 connect_timeout函数封装 17socket编程(十二) select限制 poll 18socket编程(十三) epoll使用 epoll与select、poll区别 epoll LT/ET模式 19socket编程(十四) UDP特点 UDP客户/服务基本模型 UDP回射客户/服务器 UDP注意点 20socket编程(十) udp聊天室实现 21socket编程(十六) UNIX域协议特点 UNIX域地址结构 UNIX域字节流回射客户/服务 UNIX域套接字编程注意点 22socket编程(十七) socketpair sendmsg/recvmsg UNIX域套接字传递描述符字 Linux网络编程之进程间通信篇 23进程间通信介绍(一) 进程同步与进程互斥 进程间通信目的 进程间通信发展 进程间通信分类 进程间共享信息的三种方式 IPC对象的持续性 24进程间通信介绍(二) 死锁 信号量 PV原语 用PV原语解决司机与售票员问题 用PV原语解决民航售票问题 用PV原语解决汽车租赁问题 25System V消息队列(一) 消息队列 IPC对象数据结构 消息队列结构 消息队列在内核中的表示 消息队列函数 26System V消息队列(二) msgsnd函数 msgrcv函数 27System V消息队列(三) 消息队列实现回射客户/服务器 28共享内存介绍 共享内存 共享内存示意图 管道、消息队列与共享内存传递数据对比 mmap函数 munmap函数 msync函数 29System V共享内存 共享内存数据结构 共享内存函数 共享内存示例 30System V信号量(一) 信

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值