1. 信号量的概念和作用
1.1信号量的概念和原理:
信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段。它是一个特殊的变量,只允许对它进行等待wait()和发送信号post(),并且这两种操作都是原子操作。该变量的值表示允许同时访问共享资源的最大进程数。
wait() 等待一个信号量该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。这整个操作必须是一个原子操作。
post() 挂出一个信号量。该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒,调用这个函数会使其中一个进程不在阻塞,选择机制是有进程的调度策略决定的。该操作是一个原子操作。
1.2信号量,互斥锁,条件变量的区别
- 条件变量和信号量都可以实现进程之间和线程之间的同步,不过条件变量实现进程间的同步需要借助共享内存。
- 使用条件变量时,条件满足到阻塞,不是一个原子操作,所以判断条件是否满足前应该先加锁,如果不先加锁可能出现唤醒信号丢失的情况(对于消费者,阻塞条件满足就是,缓冲池为空)。信号量使用时信号量为零到阻塞的过程是原子的。所以wait() 操作前不需要加锁。
- 当信号量的初始值为为1时,可以把他当做互斥锁使用。
1.3有名信号量和无名信号量
POSIX信号量有两种:有名信号量和无名信号量,无名信号量也被称作基于内存的信号量。有名信号量通过IPC名字进行进程间的同步,而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。
2. POSIX信号量的接口
2.1信号量的创建
(1)无名信号量的创建
无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。初始化函数如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
sem: 要初始化的信号量
pshared: pshared == 0 用于同一多线程的同步;若pshared > 0 用于多个相关进程间的同步(即由fork产生的)。
value: 信号量的初始值。
(2)有名信号量的创建
#include <semaphore.h>
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,
mode_t mode, unsigned int value);
//成功返回信号量指针,失败返回SEM_FAILED
name:是文件的路径名;
oflag :取0,O_CREAT,O_EXCL。如果为0表示打开一个已存在的信号量,如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回。此时mode和value需要指定。如果为O_CREAT | O_EXCL,表示如果信号量已存在会返回错误。
mode_t:控制新的信号量的访问权限;
value:指定信号量的初始化值。
2.2信号量的删除
#include <semaphore.h>
int sem_close(sem_t *sem);//关闭信号量
int sem_unlink(const char *name);//删除信号量
//成功返回0,失败返回-1
int sem_close(sem_t *sem); // 关闭信号量
- 当一个进程打开有名信号量时,系统会记录进程与信号的关联关系。调用 sem_close 时,会终止这种关联关系,同时信号量的进程数的引用计数减 1 。
- 进程终止时,进程打开的有名信号量会自动关闭。当进程执行 exec 系列函数时,进程打开的有名信号量会自动关闭。
- 关闭不等同于删除.
int sem_unlink(const char *name); // 删除信号量
- 将有名信号量的名字作为参数,传递给 sem_unlink ,该函数会负责将该有名信号量删除。由于系统为信号量维护了引用计数,所以只有当打开信号量的所有进程都关闭了之后,才会真正地删除。如果没有全部关闭sem_unlink调用没有任何作用。
2.3信号量的等待(P操作)
int sem_wait (sem_t *sem);
如果调用 sem_wait 函数时,信号量的当前值大于 0 ,那么sem_wait 函数立刻返回。否则 sem_wait 函数陷入阻塞,待信号量的值大于 0 之后,再执行减 1 操作,然后成功返回。
2.4信号量的释放(V操作)
int sem_post(sem_t *sem);
表示资源已经使用完毕,可以归还资源了。该函数会使信号量的值加 1 。如果发布信号量之前,信号量的值是 0 ,有多个进程正等待在信号量上,那么将无法确认哪个进程会被唤醒。
2.5获取信号量的值
int sem_getvalue(sem_t *sem, int *sval);
返回当前信号量的值,并将值写入 sval 指向的变量当 sem_getvalue 返回时,其返回的值可能已经过时了。从这个意义上讲,该接口的意义并不大。
3. 同步实例生产者消费者问题
#include<cstdio>
#include<iostream>
#include<vector>
#include<semaphore.h>
using namespace std;
#define QUEUE_MAX 3
static int i = 0;
class RingQueue{
public:
RingQueue(int capacity = QUEUE_MAX)
:_capacity(capacity)
,_step_read(0)
,_step_write(0){
_queue.resize(_capacity,0);
sem_init(&_lock, 0, 1);//信号量用于线程间互斥初始值为1.
sem_init(&_sem_idle, 0, capacity);//线程同步,初始值为空闲空间的个数
sem_init(&_sem_data, 0 , 0);//线程间同步,初始值为已用空间个数:0;
}
~RingQueue(){
sem_destroy(&_sem_data);
sem_destroy(&_sem_idle);
sem_destroy(&_lock);
}
bool push(int data){//生产者
//1. wait() 判断空闲个数_sem_idle,大于零 -1 继续,等于零 阻塞
sem_wait(&_sem_idle);
//2. wait()判断队列是否有人访问。有人阻塞,无人访问
sem_wait(&_lock);
//3. 访问共享资源 push也就是放入数据
_queue[_step_write] = data;
_step_write = (_step_write + 1) % _capacity;
//4. 解锁
sem_post(&_lock);
//5. 释放信号,_sem_data + 1,唤醒等待队列上一个阻塞的线程(消费者线程)
sem_post(&_sem_data);
return true;
}
bool pop(int *data){
// 1. wait() a判断已经有数据节点的个数_sem_data,大于0 减一继续,等于零阻塞
sem_wait(&_sem_data);
// 2. wait() 互斥判断是否有线程访问队列
sem_wait(&_lock);
// 3. 访问共享资源,改变_step_read的值
*data = _queue[ _step_read ];
_queue[_step_read] = 0;
_step_read = (_step_read + 1) % _capacity;
// 4. 解锁
sem_post(&_lock);
// 5. 释放信号,_sem_idle + 1唤醒一个等待队列上的线程,(生产者线程)
sem_post(&_sem_idle);
return true;
}
private:
std::vector<int> _queue;// 数组
int _capacity;//这是队列的容量
int _step_read;//读数据的位置
int _step_write;//写数据的位置
sem_t _lock; //用于实现互斥的信号量
sem_t _sem_idle; // 用于对空闲空间进行计数,大于0才可以写数据
sem_t _sem_data; //对具有数据的空间进行计数,大于0才能读数据
};
void* thr_product(void* grm){
RingQueue *ptr = (RingQueue*)grm;
while(1){
//生产者不断地写入数据
ptr ->push(i++);
printf("写入数据%d\n",i);//注意printf()和上边的push不是原子操作
}
return NULL;
}
void* thr_consumer(void* grm){
RingQueue *ptr = (RingQueue*)grm;
int date;
while(1){
//消费者不断地读出数据
ptr ->pop(&date);
printf("读出数据%d\n",date);
}
return NULL;
}
int main(){
RingQueue q(3);//阻塞队列的容量为3
pthread_t pro_tid[4];//4个生产者
pthread_t con_tid[4];//4个消费者
for(int i = 0; i < 4; i++){
int res = pthread_create(&pro_tid[i],NULL, thr_product, (void*)&q);
if(res != 0){
printf("创建生产者线程失败");
return -1;
}
}
for(int i = 0; i < 4; i++){
int res = pthread_create(&con_tid[i],NULL, thr_consumer, (void*)&q);
if(res != 0){
printf("创建消费者线程失败");
return -1;
}
}
pthread_join(pro_tid[0],NULL);
pthread_join(con_tid[0],NULL);
return 0;
}