【三】POSIX信号量(POSIX有名信号量、POSIX无名信号量)

(1)概述
    1.理论与SYSTE-V一样, 但在IPC上进行了封装, 和在文件系统上进行了扩展

    2.POSIX 信号量和 System V 信号量的作用是相同的,都是用于同步进程之间及线程之间的操作,以达
      到无冲突地访问共享资源的目的。

    3.POSIX 信号量的作用和 System V 信号量是一样的。但是两者在接口上有很大的区别:
        ·POSIX 信号量将创建和初始化合二为一
        ·一次只能修改一个信号量。System V 信号量其本质是信号量集,一次可以修改多个信号量。
        ·POSIX 信号量一次只能将信号量的值加 1 或减 1 。System V 信号量能够加上或减去一个大于 1 的值。
        ·POSIX 信号量并没有提供一个等待信号量变为 0 的接口, System V 信号量中, semop 函数则提供了这样的接口
        ·POSIX 信号量并没有提供 UNDO 操作,而 System V 信号量则提供了这样的操作。

    4.POSIX 信号量真正比 System V 信号量优越的地方在于, POSIX 信号量性能更好。    
        对于 System V 信号量而言,每次操作信号量,必然会从用户态陷入内核态时间上的开销很大
         POSIX 信号量,只要不存在真正的两个线程争夺一把锁的情况,那么修改信号量就只是用户态的操作,并不会牵扯到内核。
         在竞争并不激烈的情况下, POSIX 的性能要远远高于 System V 信号量。

    5.(☆)POSIX 提供了两类信号量:有名信号量和无名信号量。
        1.无名信号量,又称为基于内存的信号量,由于其没有名字,没法通过 open 操作直接找到对应的信号量,
          所以很难直接用于没有关联的两个进程之间:无名信号量多用于线程之间的同步。
        2.有名信号量由于其有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步
            操作,所以有名信号量的操作要方便一些,适用范围也比无名信号量更广。

(2)有名信号量(用于进程间的通信)

    // 有名字,多个不相干的进程可以通过名字来打开同一个信号量,从而完成同步
    ①创建、打开、关闭、删除有名型号量
        1.sem_t *sem_open(const char *name, int oflag);

        2.sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
          第二个参数 oflag 标志位支持的标志包括 O_CREAT 和 O_EXCL 标志位。如果带了 O_CREAT 标志位,则表示要创建信号量。
            mode:  创建的新信号量的访问权限
            value: 新建信号量的初始值。创建和赋初值都是由一个接口来完成的
            不要尝试创建 sem_t 结构体的副本,切记,后面所有的调用都要用通过 sem_open 返回的 sem_t 类型的指针来进行操作,而不能使用结构体的副本。

        3.int sem_close(sem_t *sem);             // 关闭信号量
            1.当一个进程打开有名信号量时,系统会记录进程与信号的关联关系。调用 sem_close 时,会终止这
              种关联关系,同时信号量的进程数的引用计数减 1 。
            2.进程终止时,进程打开的有名信号量会自动关闭。当进程执行 exec 系列函数时,进程打开的有名信号量会自动关闭。
            3.关闭不等同于删除.

        4.int sem_unlink(const char *name);    // 删除信号量
           将有名信号量的名字作为参数,传递给 sem_unlink ,该函数会负责将该有名信号量删除。由于系统
            为信号量维护了引用计数,所以只有当打开信号量的所有进程都关闭了之后,才会真正地删除。

        5.注意
           如果程序结尾没有删除Posix信号量, 第二次运行时就只会打开前面创建的信号量;
            而信号量的初值是在信号量创建时设置的; 
        ==> 第二次运行时,信号量的初值可能不是我们想要的

    ②信号量的使用
        /*  信号量的使用,总是和某种可用资源联系在一起的。创建信号量时的 value 值,其实指定了对应资
            源的初始个数。当申请该资源时,需要先调用 sem_wait 函数;当发布该资源或使用完毕释放该资源时,
            则调用 sem_post 函数。
        */
        1.等待信号量
          int sem_wait(sem_t *sem);
            如果调用 sem_wait 函数时,信号量的当前值大于 0 ,那么 sem_wait 函数立刻返回。否则 sem_wait 函
            数陷入阻塞,待信号量的值大于 0 之后,再执行减 1 操作,然后成功返回。

          int sem_trywait(sem_t *sem);
            sem_trywait 会尝试将信号量的值减 1 ,如果信号量的值大于 0 ,那么该函数将信号量的值减 1 之后会
            立刻返回。如果信号量的当前值为 0 ,那么 sem_trywait 也不会陷入阻塞,而是立刻返回失败,并置 errno
            为 EAGAIN 。

          int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
            有限期等待,第二个参数为一个绝对时间。可以使用 gettimeofday 函数获取到 struct timeval 类型的当前时间,然后
            将 timeval 转换成 timespec 类型的结构体,最后在该值上加上想等待的时间。
            如果超过了等待时间,信号量的值仍然为 0 ,那么返回 -1 ,并置 errno 为 ETIMEOUT 。

        2.发布信号量
          int sem_post(sem_t *sem);
            表示资源已经使用完毕,可以归还资源了。该函数会使信号量的值加 1 。
            如果发布信号量之前,信号量的值是 0 ,有多个进程正等待在信号量上,那么将无法确认哪个进程会被唤醒。

        3.获取信号量的值
          int sem_getvalue(sem_t *sem, int *sval);
            返回当前信号量的值,并将值写入 sval 指向的变量
            当 sem_getvalue 返回时,其返回的值可能已经过时了。从这个意义上讲,该接口的意义并不大。

(3)无名信号量(用于多线程间的同步互斥)
    ①为什么要使用无名信号量
        1.无名信号量,由于其没有名字,所以适用范围要小于有名信号量。只有将无名信号量放在多个进
          程或线程都共同可见的内存区域时才有意义,否则协作的进程无法操作信号量,达不到同步或互斥的目的。
        2.所以一般而言,无名信号量多用于线程之间。因为线程会共享进程的地址空间,所以访问共同的无名信号量是很容易办到的事情。
        3. 或者将信号量创建在共享内存内,多个进程通过操作共享内存的信号量达到同步或互斥的目的。

    ②初始化.使用无名信号量
        int sem_init(sem_t *sem, int pshared, unsigned int value);
            pshared: 用于声明信号量是在线程间共享还是在进程间共享。
                  0 表示在线程间共享,非零值则表示信号量将在进程间共享。
            1.要想在进程间共享,无名信号量必须位于共享内存区域内。
            2.对于线程间共享的信号量,线程组退出了,无名信号量也就不复存在了。
            3.无名信号量初始化以后,就可以像操作有名信号量一样操作无名信号量了。
        int sem_wait(sem_t *sem);
        int sem_trywait(sem_t *sem);
        int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
        int sem_post(sem_t *sem);
        int sem_getvalue(sem_t *sem, int *sval);
              
    ③销毁无名信号量
        int sem_destroy(sem_t *sem);
            1.销毁 sem_init 函数初始化的无名信号量。
            2.只有在所有进程都不会再等待一个信号量时,它才能被安全销毁。

 (4)示例代码

#include 
#include 
#include 
#include 
#include 
#include 

#define CUR printf("cur %d\n", __LINE__)
//#define CUR
#define USED_TO_PTHREADS 0
#define USED_TO_PROCESSS 1

static sem_t sem_product;
static sem_t sem_5;
static sem_t sem_1;
static int val = 0;

void *productor(void *arg)
{	
	while(1) {
		sem_post(&sem_product);				// 生产了一个产品
		sem_getvalue(&sem_product, &val);	// 获取无名信号量的值
		printf("prod: %d\n", val); 
		if (val == 5)
		{	// 唤醒阻塞的消费者
			sem_post(&sem_5);
			// 生产5个后不再生产, 直到消费到只有1个产品
			sem_wait(&sem_1);
		}
		sleep(1);		// 每1s生产一个产品
	}
	pthread_exit(NULL);
}
void *consumer1(void *arg)
{		
	while(1) {
		sem_wait(&sem_5);		// 未达到5个之前, 消费者阻塞
		sem_wait(&sem_product);				// 消费了一个产品
		sem_getvalue(&sem_product, &val);	// 获取无名信号量的值
		printf("cons1: %d\n", val); 
		if (val == 1)		
		{	// 唤醒阻塞的生产者
			sem_post(&sem_1);	// 通知生产
			sem_wait(&sem_5);	// 阻塞到生产了5个产品
		}
		sem_post(&sem_5);		// 从5个消费到一个的过程中不应该阻塞消费者
		sleep(1);								
	}
	pthread_exit(NULL);
}
void *consumer2(void *arg)
{		
	while(1) {
		sem_wait(&sem_5);		// 未达到5个之前, 消费者阻塞
		sem_wait(&sem_product);				// 消费了一个产品
		sem_getvalue(&sem_product, &val);	// 获取无名信号量的值
		printf("cons2: %d\n", val); 
		if (val == 1)		
		{	// 唤醒阻塞的生产者
			sem_post(&sem_1);	// 通知生产
			sem_wait(&sem_5);	// 阻塞到生产了5个产品
		}
		sem_post(&sem_5);		// 从5个消费到一个的过程中不应该阻塞消费者
		sleep(1);								
	}
	pthread_exit(NULL);
}
int main(int argc, char **argv)
{
	pthread_t thid, thid1, thid2;

	// 创建POSIX无名信号量
	sem_init(&sem_product, USED_TO_PTHREADS, 0);		/* 最开始没有产品 */
	sem_init(&sem_5, USED_TO_PTHREADS, 0);			/* 最开始没有5个产品 */
	sem_init(&sem_1, USED_TO_PTHREADS, 0);			/* 最开始没有达到一个产品 */
	
	if(pthread_create(&thid, NULL, productor, NULL))
		perror("pthread_create err");
	if(pthread_create(&thid1, NULL, consumer1, NULL))
		perror("pthread_create err");
	if(pthread_create(&thid2, NULL, consumer2, NULL))
		perror("pthread_create err");
	
	pthread_join(thid, NULL);	// 线程未退出时陷入阻塞
	pthread_join(thid1, NULL);
	pthread_join(thid2, NULL);
	
	// 销毁无名信号量
	sem_destroy(&sem_product);
	sem_destroy(&sem_5);
	sem_destroy(&sem_1);
	
	return 0;
}

/* 要求: 1.生产5个后广播消费者使用
		 2.消费到还剩一个后,通知生产者生产
		 3.生产期间不准消费, 消费期间不准生产
		 4.生产者0.5s生产一个, 每个消费者1s消费一个
		 5.使用无名信号量实现
*/
/* 运行结果 : 	
	book@gui_hua_shu:$ ./a.out
		// 生产期间, 所有消费者阻塞在sem_5
	prod: 1
	prod: 2
	prod: 3
	prod: 4
	prod: 5		
		
	cons1: 4	// 生产5个后, 生产者阻塞在sem_1, cons1抢夺到sem_5, cons2阻塞
				 用完后, cons1释放了sem_5
	cons2: 3	// 线程cons2抢到了sem_5
				 用完后, cons2释放了sem_5
	cons1: 2
	cons2: 1	// cons2释放了sem_1, cons2阻塞在sem_5
				// 这时cons1也阻塞在了sem_5
	// 循环
	prod: 2
	prod: 3
	prod: 4
	prod: 5
.................................*/

 

你可能感兴趣的:(第11章进程间通信:,POSIX,IPC)