简 述: 上一篇中讲解了“条件变量 + 互斥量”的组合使用,演示了 “生产者-消费者”模型。本篇讲解 互斥量的升级版:信号量(信号灯) 的理解和使用。互斥量与信号量的关系,可以简单理解为 c 和 c++ 的关系。信号量的使用的步骤,也是和前面的互斥量很像,不过这次的头文件改为了 #include <semaphore.h>
:
- sem_t sem; //定义变量
- sem_wait(); //加锁
- …其他代码
- sem_post(); //解锁
- sem_destroy(); //销毁
说明:
本例子是在 Linux 下面运行成功的,编译时候,时候需要加参数 -pthread
。
若是想要在 Mac 运行改程序,需要改写替换部分函数(mac 不支持其中的部分函数)
[TOC]
本文初发于 “偕臧的小站“,同步转载于此。
编程环境:
💻: uos20
📎 gcc/g++ 8.3
📎 gdb8.0
💻: MacOS 10.14
📎 gcc/g++ 9.2
📎 gdb8.3
信号量(信号灯):
简单理解为里面具有多个互斥量的集合。是加强版的互斥锁。
其他:
sem_t sem;
int sem_init(sem_t *sem, int pshared, unsigned int value);
之间的一些解释:
sem 变量和 函数 sem_init 中的 sem 参数是同一个;在函数里面,第一个参数 sem 实际是和第三个参数 value 关联的;表面上对 sem 进行修改,实际上是修改关联的 value 的值,对其进行 ++ 或 – 操作。(嗯嗯,原理就这么理解就行)。
使用步骤:
sem_t sem;
初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 参数:
- pshared:
- 0 - 线程同步
- 1 - 进程同步
- value:
- 最多有几个线程操作共享数据
- pshared:
- 参数:
加锁:
//调用一次相当于对 sem 做了一次 -- 操作 int sem_wait(sem_t *sem); //加锁; => 如果 sem 值为 0,线程会阻塞 int sem_trywait(sem_t *sem); //尝试加锁; => sem == 0,加锁失败,不阻塞,直接返回 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //限时尝试加锁
解锁:
int sem_post(sem_t *sem); //相当于对于 sem 做了 ++ 操作
销毁信号量
int sem_destroy(sem_t *sem);
“生产者-消费者”例子:
将上一篇的例子改一改,直接使用信号量来写这个例子“生产者-消费者”。本篇文章例子如下:
理论模型:
代码分析:
多看一下理论模型的那个伪代码的流程图,多体会揣摩其中的箭头的流向,且一开始这设置为消费者为阻塞,当生产者生产之后,为其解锁。
代码实现:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t semProducer;
sem_t semCustomer;
typedef struct node //节点结构
{
int data;
struct node* next;
} Node;
Node* g_head = nullptr; //永远指向链表头部的指针
void* funProducer(void* arg); //生产者--添加一个头结点
void* funCustomer(void* arg); //消费者--删除一个头结点
int main(int argc, char *argv[])
{
pthread_t p1;
pthread_t p2;
sem_init(&semProducer, 0, 4); //初始化生产者线程信号量, (赋予 4 个,对比下一行,让生产者先运行)
sem_init(&semCustomer, 0, 0); //初始化消费者线程信号量, (赋予 0 个, 一开始就让消费者处于阻塞状态)
pthread_create(&p1, nullptr, funProducer, nullptr); //创建生产者线程
pthread_create(&p2, nullptr, funCustomer, nullptr); //创建消费者线程
pthread_join(p1, nullptr); //阻塞回收子线程
pthread_join(p2, nullptr);
sem_destroy(&semProducer); //销毁生产者信号量
sem_destroy(&semCustomer); //销毁消费者信号量
return 0;
}
void* funProducer(void* arg)
{
while (true) {
sem_wait(&semProducer); //semProducer--, == 0, 则阻塞
Node* pNew = new Node();
pNew->data = rand() % 1000;
pNew->next = g_head;
g_head = pNew;
printf("-----funProducer(生产者): %lu, %d\n", pthread_self(), pNew->data);
sem_post(&semCustomer); // semCustomer++
sleep(rand() % 3); //随机休息 0~2 s
}
return nullptr;
}
void* funCustomer(void* arg)
{
while (true) {
sem_wait(&semCustomer); //semCustomer--, == 0, 则阻塞
Node* pDel = g_head;
g_head = g_head->next;
printf("-----funCustomer(消费者): %lu, %d\n", pthread_self(), pDel->data);
delete pDel;
sem_post(&semProducer); // semProducer++
}
return nullptr;
}
运行结果:
这个例子是在 uos20 上面运行的成功的;编译的时候,记得加上参数 -pthread
。
Mac 下对 sem_init()/sem_destory() 不支持:
注意:
MacOS 不支持 sem_init()
和 sem_destroy()
;这个例子若是想在 mac 下编译通过,需要自行修改替换相关的函数。
sem_init(&sem, 0, 1)
改成sem_open("sem", O_CREAT|O_EXCL, S_IRWXU, 0)
sem_destory(&sem)
改成sem_unlink("sem");
- 且支持
pthread_mutex_init(&mutex, NULL)
却不支持pthread_mutex_destory(&mutex)
Mac 的该文件在 /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/semaphore.h
路径,可以查看到该头文件的源码,附上详细 mac 下该库源码:
#ifndef _SYS_SEMAPHORE_H_
#define _SYS_SEMAPHORE_H_
typedef int sem_t;
/* this should go in limits.h> */
#define SEM_VALUE_MAX 32767
#define SEM_FAILED ((sem_t *)-1)
#include <sys/cdefs.h>
__BEGIN_DECLS
int sem_close(sem_t *);
int sem_destroy(sem_t *) __deprecated;
int sem_getvalue(sem_t * __restrict, int * __restrict) __deprecated;
int sem_init(sem_t *, int, unsigned int) __deprecated;
sem_t * sem_open(const char *, int, ...);
int sem_post(sem_t *);
int sem_trywait(sem_t *);
int sem_unlink(const char *);
int sem_wait(sem_t *) __DARWIN_ALIAS_C(sem_wait);
__END_DECLS
#endif /* _SYS_SEMAPHORE_H_ */
下载地址:
欢迎 star 和 fork 这个系列的 linux 学习,附学习由浅入深的目录。