POSIX提供了两种信号量:有名信号量和无名信号量。 这两种信号量的本质都是一样的。
本质上为计数器 + PCB等待队列 + 一堆接口(等待接口,唤醒接口)
计数器:本质上是对资源的计数
又称为基于内存的信号量, 由于其没有名字, 没法通过open操作直接找到对应的信号量, 所以很难直接用于没有关联的两个进程之间。 无名信号量多用于线程之间的同步。
有名信号量由于其有名字, 多个独立的进程可以通过名字来打开同一个信号量, 从而完成同步操作, 所以有名信号量的操作要方便一些, 适用范围也比无名信号量更广。
#include
#include
#include
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数 | 说明 |
---|---|
oflag | 可取O_CREAT和O_EXCL,取O_CREAT时,表示要创建信号量 |
mode | 创建新的信号量的权限,标志位和open函数一样,mode的参数也会根据进程的umask来取掩码 |
value | 新创建信号量的初始值 |
当sem_open函数失败时, 返回SEM_FAILED, 并且设置errno。
#include
int sem_close(sem_t *sem);
当一个进程打开有名信号量时, 系统会记录进程与信号的关联关系。 调用sem_close时, 会终止这种关联关系, 同时信号量的进程数的引用计数减1。
进程终止时, 进程打开的有名信号量会自动关闭。 当进程执行exec系列函数时, 进程打开的有名信号量会自动关闭。
注意:关闭不等于删除。
#include
int sem_unlink(const char *name);
将有名信号量的名字作为参数, 传递给sem_unlink, 该函数会负责将该有名信号量删除。 由于系统为信号量维护了引用计数, 所以只有当打开信号量的所有进程都关闭了之后, 才会真正地删除。
无名信号量, 由于其没有名字, 所以适用范围要小于有名信号量。 只有将无名信号量放在多个进程或线程都共同可见的内存区域时才有意义, 否则协作的进程无法操作信号量, 达不到同步或互斥的目的。 所以一般而言, 无名信号量多用于线程之间。 因为线程会共享地址空间, 所以访问共同的无名信号量是很容易办到的事情。 或者将信号量创建在共享内存内, 多个进程通过操作共享内存的信号量达到同步或互斥的目的。
#include
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数 | 说明 |
---|---|
sem | 传入信号量的地址 |
pshared | 0:用于线程间、全局变量,非0:用于进程间,信号量所用资源在共享内存中开辟 |
value | 资源的个数,本质上是初始化信号量计数器的 |
无名信号量的生命周期是有限的, 对于线程间共享的信号量, 线程组退出了,无名信号量也就不复存在了。 对于进程间共享的信号量, 信号量的持久性与所在的共享内存的持久性一样。
无名信号量初始化以后, 就可以像操作有名信号量一样操作无名信号量了。
#include
int sem_destroy(sem_t *sem);
sem_destroy用于销毁sem_init函数初始化的无名信号量。 只有在所有进程都不会再等待一个信号量时, 它才能被安全销毁。
信号量的使用, 总是和某种可用资源联系在一起的。 创建信号量时的value值,其实指定了对应资源的初始个数。 当申请该资源时, 需要先调用sem_wait函数; 当发布该资源或使用完毕释放该资源时, 则调用sem_post函数。
#include
int sem_wait(sem_t *sem);
注意:调用该接口的执行流会对计数器进行减1。
两种情况:
int sem_trywait(sem_t *sem);
sem_trywait会尝试将信号量的值减1, 如果信号量的值大于0, 那么该函数将信号量的值减1之后会立刻返回。 如果信号量的当前值为0, 那么sem_trywait也不会陷入阻塞, 而是立刻返回失败, 并置errno为EAGAIN。
EAGAIN:当前操作不会执行,但不会阻塞。
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
如果超过了等待时间, 信号量的值仍然为0, 那么返回-1, 并置errno为ETIMEOUT。
ETIMEOUT:超出等待时间限制。
sem_post函数用于发布信号量, 表示资源已经使用完毕, 可以归还资源了。 该函数会使信号量的值加1。
#include
int sem_post(sem_t *sem);
如果发布信号量之前, 信号量的值是0, 并且已经有进程或线程正等待在信号量上, 此时会有一个进程被唤醒, 被唤醒的进程会继续sem_wait函数的减1操作。如果有多个进程正等待在信号量上, 那么将无法确认哪个进程会被唤醒。
当函数调用成功时, 返回0; 失败时, 返回-1, 并置errno。 当参数sem并不指向合法的信号量时, 置errno为EINVAL; 当信号量的值超过上限时, 置errno为EOVERFLOW。
注意:计数器值为负,则代表还有多少个执行流在PCB等待队列中等待。
代码:下述代码,可随意更改资源数量和线程数量,结果都是可行的,附注释
#include
#include
#include
#include
#include
#include
#include
#include
#define REOURSECOUNT 4 //资源数量
#define PTHREADCOUNT 2 //线程数量
class ModelOfProdConsByPosix
{
public:
ModelOfProdConsByPosix()//构造
:_queue(REOURSECOUNT)
{
_capacity = REOURSECOUNT;
sem_init(&_lock,0,1);//0代表线程间,1为计数器的初始值
sem_init(&_prod,0,REOURSECOUNT);//生产者,最开始计数器初始值为4,表示可以有4个资源可用
sem_init(&_cons,0,0);//消费者
_write_pos = _read_pos = 0;
}
~ModelOfProdConsByPosix()//析构
{
sem_destroy(&_lock);
sem_destroy(&_prod);
sem_destroy(&_cons);
}
void push(int& data)//插入数据
{
sem_wait(&_prod);
sem_wait(&_lock);
_queue[_write_pos] = data;//在这里我们不需要循环判断,因为可用资源为RESOURCECOUNT个,每写入数据之后,计数器就会减1
_write_pos = (_write_pos + 1) % _capacity;
sem_post(&_lock);
sem_post(&_cons);
}
void pop(int &data)//删除数据
{
sem_wait(&_cons);
sem_wait(&_lock);
data = _queue[_read_pos];//pop时,也不需要循环判断,生产者给消费者发送信号量之后,其计数器就会加1,当pop之后,其计数器就会减1,就达到了同步和互斥
_read_pos = (_read_pos + 1) % _capacity;
sem_post(&_lock);
sem_post(&_prod);
}
private:
std::vector<int> _queue;
int _capacity;
sem_t _lock;
sem_t _prod;
sem_t _cons;
int _write_pos;
int _read_pos;
};
void *ConsumerStart(void *arg)
{
ModelOfProdConsByPosix *cp = (ModelOfProdConsByPosix*) arg;
int data = 0;
while(1)
{
cp->pop(data);
printf("i am pid : %d,i consume : %d\n",(int)syscall(SYS_gettid),data);
}
}
void *ProductStart(void *arg)
{
ModelOfProdConsByPosix *cp = (ModelOfProdConsByPosix*) arg;
int data = 0;
while(1)
{
cp->push(data);
printf("i am pid : %d,i product : %d\n",(int)syscall(SYS_gettid),data);
++data;
}
}
int main()
{
ModelOfProdConsByPosix *cp = new ModelOfProdConsByPosix;
pthread_t cons[PTHREADCOUNT],prod[PTHREADCOUNT];
for(int i = 0; i < PTHREADCOUNT;++i)
{
int ret = pthread_create(&cons[i],NULL,ConsumerStart,(void*)cp);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
ret = pthread_create(&prod[i],NULL,ProductStart,(void*)cp);
if(ret < 0)
{
perror("pthread_create");
return -1;
}
}
for(int i = 0;i < PTHREADCOUNT;++i)
{
pthread_join(cons[i],NULL);
pthread_join(prod[i],NULL);
}
return 0;
}
结果: