信号量、互斥锁和条件变量的区别

一、

1、互斥锁总是必须由给其上锁的线程解锁,信号量的挂出确不必由执行过它的等待操作的同一线程执行。

                                生产者与消费者伪代码

2、互斥锁要么被锁住,要么被解锁(二值状态,类似于二值信号量)

3、既然信号量有一个与之关联的状态(它的数值),那么信号量的挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么信号将丢失。

ps:提供信号量的原因是,在进程间同步的情况下,若没有涉及到共享内存区时,需要使用信号量。

二、

  1、posix提供量中信号量:有名信号量和基于内存的信号量,后者被称为无名信号量。

  

有名信号量如下图所示:

  

内存信号量(无名信号量)如下图所示:

    

三、有名信号量

  sem_open:创建一个新的有名信号量或者打开一个已经存在的有名信号量。有名信号量既可用于线程间同步,也可用于进程间同步。

头文件:#include <semaphore.h>
函数原型:sem_t *sem_open(const char *name,int oflag,mode_t mode,unsigned int value);
参数:
name 信号量的外部名字
oflag 选择创建或打开一个现有的信号量
mode 权限位
value 信号量初始值
oflag参数可以是0、O_CREAT(创建一个信号量)或O_CREAT|O_EXCL(如果没有指定的信号量就创建),如果指定了O_CREAT,那么第三个和第四个参数是需要的;其中mode参数指定权限位,value参数指定信号量的初始值,通常用来指定共享资源的书面。该初始不能超过SEM_VALUE_MAX,这个常值必须低于为32767。二值信号量的初始值通常为1,计数信号量的初始值则往往大于1。

如果指定了O_CREAT(而没有指定O_EXCL),那么只有所需的信号量尚未存在时才初始化它。所需信号量已存在条件下指定O_CREAT不是一个错误。该标志的意思仅仅是“如果所需信号量尚未存在,那就创建并初始化它”。但是所需信号量等已存在条件下指定O_CREAT|O_EXCL却是一个错误。

sem_open返回指向sem_t信号量的指针,该结构里记录着当前共享资源的数目。
 

sem_close用于关闭打开着的信号量:

#include <semaphore.h>

int sem_close(sem_t *sem)

sem_unlink将有名信号量从系统中删除

#include <semaphore.h>

int sem_unlink(const char *name)
sem_wait是一个函数,也是一个原子操作,它的作用是从信号量的值减去一个“1”,但它永远会先等待该信号量为一个非零值才开始做减法。也就是说,如果你对一个值为2的信号量调用sem_wait(),线程将会继续执行,将信号量的值将减到1。
如果对一个值为0的信号量调用sem_wait(),这个函数就会原地等待直到有其它线程增加了这个值使它不再是0为止。如果有两个线程都在sem_wait()中等待同一个信号量变成非零值,那么当它被第三个线程增加 一个“1”时,等待线程中只有一个能够对信号量做减法并继续执行,另一个还将处于等待状态。sem_trywait(sem_t *sem)是函数sem_wait的非阻塞版,它直接将信号量sem减1,同时返回错误代码EAGAIN。
#include <semaphore.h>

int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);

sem_post是给信号量的值加上一个“1”,它是一个“原子操作”---即同时对同一个信号量做加“1”操作的两个线程是不会冲突的;

#include <semaphore.h>
int sem_post(sem_t *sem);

sem_getvalue,计算机术语,是把 sem 指向的信号量当前值放置在 sval 指向的整数上。 如果有一个或多个进程或线程当前正在使用 sem_wait(3) 等待信号量,POSIX.1-2001 允许返回两种结果在 sval 里:要么返回 0;要么返回一个负值,它的绝对等于当前正在 sem_wait(3) 里阻塞的进程和线程数。Linux 选择了前面的行为(返回零)。

#include <semaphore.h>

int sem_getvalue(sem_t *sem,int *sval);

posix有名信号量至少是随内核持续性的,因此可以跨多个进程操作他们。

 四、无名信号量

 之前的内容处理的是posix有名信号量的内容,这些信号量是由一个name参数标识的,它通常指代文件系统中的某个文件。然而posix也提供了基于内存的信号量。

int sem_init(sem_t *sem, int pshared, unsigned int value);
如果 pshared 是0,那么初始化的信号量是在同一个进程的各个线程间共享的。
如果 pshared 是非零值,那么信号量将在进程之间共享,并且应该定位共享内存区域(见 shm_open(3)、mmap(2) 和 shmget(2))。因为通过 fork(2) 
创建的孩子继承其父亲的内存映射,因此它也可以见到这个信号量。所有可以访问共享内存区域的进程都可以用 sem_post(3)、sem_wait(3) 等等操作信号量。
初始化一个已经初始的信号量其结果未定义。 参数 sem :指向信号量对象   pshared : 指明信号量的类型。不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享。   value : 指定信号量值的大小 返回值 sem_init() 成功时返回
0;错误时,返回 -1,并把 errno 设置为合适的值。 错误 EINVAL value 超过 SEM_VALUE_MAX。 ENOSYS pshared 非零,但系统还没有支持进程共享的信号量。

摧毁信号量:

#include <semaphore.h>

int sem_destroy(sem_t *sem);

  进程间共享基于内存信号量的规则:信号量本身(其作为sem_init第一个参数的sem_t数据类型变量)必须驻留在由所有希望共享它的进程所共享的内存区中,而且sem_init的第二个参数必须是1。

  在父进程中打开的任何信号量仍应在子进程中打开。如下:

  

sem_t *sem;

sem = sem_open("sem name",O_CREATE|O_EXCL,FILE_MODE,0);
if(childpid = fork() == 0)
{
        sem_wait(sem);      
}
else
{
        sem_post(sem);  
}

  

原文地址:https://www.cnblogs.com/rabbit0212/p/11231653.html