信号量用于进程或线程间同步,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_wait和sem_post操作,使用完成后sem_destroy接口进行销毁,下一节介绍接口的使用
#include /* For O_* constants */
#include /* For mode constants */
#include
/**
* @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.
#include
/**
* @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.
#include
/**
* @brief 信号量的值加1
*
* @params sem 信号量文件描述符
* @returns 成功返回0,失败返回-1
**/
int sem_post(sem_t *sem);
# 编译链接选项 -pthread
Link with -pthread.
#include
/**
* @brief 关闭信号量,系统为当前进程分配的信号量资源会被释放。
*
* @params sem 信号量文件描述符
* @returns 成功返回0,失败返回-1
**/
int sem_close(sem_t *sem);
# 编译链接选项 -pthread
Link with -pthread.
#include
/**
* @brief 销毁信号量,只有当所有进程都关闭信号量之后才开始销毁工作
*
* @params name 信号量名字
* @returns 成功返回0,失败返回-1
**/
int sem_unlink(const char *name);
# 编译链接选项 -pthread
Link with -pthread.
#include
/**
* @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.
#include
/**
* @brief 销毁无名信号量
*
* @params sem 要销毁的信号量
* 如果有其它线程或进程阻塞在sem上,此时销毁会产生未定义的行为
*
* @returns 成功返回0,失败返回-1
**/
int sem_destroy(sem_t *sem);
# 编译链接选项 -pthread
Link with -pthread.
// 生产者
#include /* For O_* constants */
#include /* For mode constants */
#include
#include
#include
#include
#include
#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;
}
// 消费者
#include /* For O_* constants */
#include /* For mode constants */
#include
#include
#include
#include
#include
#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;
}
default:
gcc -o sem_post sem_post.c -pthread
gcc -o sem_wait sem_wait.c -pthread
clean:
rm -rf sem_wait sem_post
创建两个线程,分别用于生产和消费
#include
#include
#include
#include
// 消费者
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;
}
1. https://www.man7.org/linux/man-pages/man7/sem_overview.7.html
2. 《Linux环境编程 从应用到内核》
================================================================================================
Linux应用程序、内核、驱动、后台开发交流讨论群(745510310),感兴趣的同学可以加群讨论、交流、资料查找等,前进的道路上,你不是一个人奥^_^。