并发编程之信号量

0.引入

一个链接来理解信号量

Posix 信号量与System v信号量的区别

进程间通信笔记(7)—SystemV信号量

1.概念

信号量是进化版的互斥锁,由于互斥锁的粒度比较大,如果我们希望在多线程间对某一个对象的部分数据进行共享,使用互斥锁是没有办法实现
的,只能将整个数据对象锁住,这样虽然达到了多线程操作共享数据是保证数据正确性的目的,却无形中导致线程的并发性下降。线程从并行执行
变成了串行执行,与直接使用使用单进程无差异。互斥量是相对一种折中的处理方式,既能保证同步,数据不混乱,又能提高线程并发.

2.主要应用函数及数据类型

头文件:
       #include <semaphore.h>
函数定义:
       int sem_init(sem_t *sem, int pshared, unsigned int value);
       int sem_destroy(sem_t *sem);//销毁信号量
       int sem_wait(sem_t *sem);//可以类比互斥锁中的lock,信号量>0,对信号量进行--;信号量=0,则造成阻塞
       int sem_trywait(sem_t *sem);//可以类比互斥锁中的try_lock,成功时对信号量进行--
       int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
       int sem_post(sem_t *sem);//可以类比互斥锁中的unlock,对信号量进行++,同时唤醒阻塞在信号量上的线程
注意:  Link with -pthread.
返回值:以上6个函数的返回值都是成功返回0,失败返回-1,同时设置errno,注意它们米有pthread前缀.  
       sem_t类型,本质仍然是结构体,但是应用期间可简单看作整数,简单使用时可忽略实现细节.

经搜内核代码,给出set_t的具体定义:
在semaphore.h文件中,sem_t的定义是(参照版本glibc-2.3.5)
#ifdef __ILP32__
# define __SIZEOF_SEM_T	16
#else
# define __SIZEOF_SEM_T	32
#endif
typedef union
{
  char __size[__SIZEOF_SEM_T];
  long long int __align;
} sem_t;

3.接口阅读

3.1 SEM_INIT

int sem_init(sem_t *sem, int pshared, unsigned int value);
简单描述:
    作用是初始化一个信号量.
    参数1:sem信号量
    参数2:pshared取0用于线程间,取非0(一般为1)用于进程间
    参数3:value指定信号初始值.

官方描述:

DESCRIPTION
sem_init() initializes the unnamed semaphore at the address pointed to by sem.  The value argument specifies the 
initial value for the semaphore.

The pshared argument indicates whether this semaphore is to be shared between 
the threads of a process, or between processes.

If  pshared  has the value 0, then the semaphore is shared between the threads 
of a process, and should be located at some address that is visible to all 
threads (e.g., a global variable, or a variable allocated dynamically on the heap).

If pshared is nonzero, then the semaphore is shared between processes, and should be located in a region of shared 
memory (see shm_open(3), mmap(2),  and  shmget(2)).   
(Since  a  child  created  by fork(2)  inherits  its parent's memory mappings, it can also access the semaphore.)  
Any process that can access the shared memory region can operate on the semaphore using sem_post(3), sem_wait(3),etc.

Initializing a semaphore that has already been initialized results in undefined behavior.
RETURN VALUE
       sem_init() returns 0 on success; on error, -1 is returned, and errno is set to indicate the error.

ERRORS
       EINVAL value exceeds SEM_VALUE_MAX.
       ENOSYS pshared is nonzero, but the system does not support process-shared semaphores (see sem_overview(7)).


我的翻译: 
描述:
sem_init()用于对指向sem地址的未命名的信号量的初始化,这个值确定了信号量的初始值.
      pshared这个参数用来区分这个信号量是被同一进程中的各个线程之间所共享还是被不同的进程之间所共享.
(这个参数隐含着的含义就有:信号量可以用于线程间同步,来可以用于进程间同步)
      如果pshared这个变量的值为0,那么这个信号量将为同一进程的多线程间所共享,此时这个信号量应该位于一个每个线程都可以访问到
的地方.(比如说,我们可以将其设置成一个全局变量,或者说是向堆区上动态申请分配内存的一个变量).
      如果pshared这个变量的值非0,那么这个信号将为多个进程所共享,此时这个信号量的地址应该在一个共享内存中.
(可以参照shm_open,mmap,shget等).另外由fork()函数创建的子进程继承了其父亲的内存映射关系,它也可以对信号量进程访问。
任何一个可以访问共享内存区域的进程都可以用sem_post和sem_wait等函数对信号量进行操作.
      初始化一个已经被初始化过的信号量会导致不可描述的现象.
      第三个参数value标识这个信号量可以被几个线程所共享而不发生阻塞(这里的理解有问题吗?)。
返回值:成功返回0,失败返回-1,并且将错误值记录在errno中(此时用perror来检测错误原因).
错误值:EINVAL表示value超过了SEM_VALUE_MAX这个值.
       ENOSYS表示pshare的值是一个非0值,但是这个系统却不支持进程间共享信号量(纳尼:还有这种系统吗?).
         

3.2 SEM_WAIT

SEM_WAIT(3)     Linux Programmer's Manual       SEM_WAIT(3)                                                            

NAME
       sem_wait, sem_timedwait, sem_trywait - lock a semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_wait(sem_t *sem);

       int sem_trywait(sem_t *sem);

       int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

       Link with -pthread.

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       sem_timedwait(): _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600

DESCRIPTION
       sem_wait() decrements (locks) the semaphore pointed to by sem.  If the semaphore's value is greater than zero, then the decrement proceeds, and the function returns, immediately.
       If the semaphore currently has the value zero, then the call blocks until either it becomes possible to perform the decrement (i.e., the semaphore value rises above zero),  or  a
       signal handler interrupts the call.

       sem_trywait() is the same as sem_wait(), except that if the decrement cannot be immediately performed, then call returns an error (errno set to EAGAIN) instead of blocking.

       sem_timedwait()  is the same as sem_wait(), except that abs_timeout specifies a limit on the amount of time that the call should block if the decrement cannot be immediately per‐
       formed.  The abs_timeout argument points to a structure that specifies an absolute timeout in seconds and nanoseconds since the Epoch,  1970-01-01  00:00:00  +0000  (UTC).   This
       structure is defined as follows:

           struct timespec {
               time_t tv_sec;      /* Seconds */
               long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
           };

       If  the  timeout  has  already  expired  by the time of the call, and the semaphore could not be locked immediately, then sem_timedwait() fails with a timeout error (errno set to
       ETIMEDOUT).

       If the operation can be performed immediately, then sem_timedwait() never fails with a timeout error, regardless of the  value  of  abs_timeout.   Furthermore,  the  validity  of
       abs_timeout is not checked in this case.

RETURN VALUE
       All of these functions return 0 on success; on error, the value of the semaphore is left unchanged, -1 is returned, and errno is set to indicate the error.

ERRORS
       EINTR  The call was interrupted by a signal handler; see signal(7).

       EINVAL sem is not a valid semaphore.

       The following additional error can occur for sem_trywait():

       EAGAIN The operation could not be performed without blocking (i.e., the semaphore currently has the value zero).

       The following additional errors can occur for sem_timedwait():

       EINVAL The value of abs_timeout.tv_nsecs is less than 0, or greater than or equal to 1000 million.

       ETIMEDOUT
              The call timed out before the semaphore could be locked.

ATTRIBUTES
       For an explanation of the terms used in this section, see attributes(7).

       ┌───────────────────────────┬───────────────┬─────────┐
       │Interface                  │ Attribute     │ Value   │
       ├───────────────────────────┼───────────────┼─────────┤
       │sem_wait(), sem_trywait(), │ Thread safety │ MT-Safe │
       │sem_timedwait()            │               │         │
       └───────────────────────────┴───────────────┴─────────┘

3.3 SEM_POST

SEM_POST(3)                                                                     Linux Programmer's Manual                                                                     SEM_POST(3)

NAME
       sem_post - unlock a semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_post(sem_t *sem);

       Link with -pthread.

DESCRIPTION
       sem_post()  increments  (unlocks)  the  semaphore pointed to by sem.  If the semaphore's value consequently becomes greater than zero, then another process or thread blocked in a
       sem_wait(3) call will be woken up and proceed to lock the semaphore.

RETURN VALUE
       sem_post() returns 0 on success; on error, the value of the semaphore is left unchanged, -1 is returned, and errno is set to indicate the error.

ERRORS
       EINVAL sem is not a valid semaphore.

       EOVERFLOW
              The maximum allowable value for a semaphore would be exceeded.

ATTRIBUTES
       For an explanation of the terms used in this section, see attributes(7).

       ┌───────────┬───────────────┬─────────┐
       │Interface  │ Attribute     │ Value   │
       ├───────────┼───────────────┼─────────┤
       │sem_post() │ Thread safety │ MT-Safe │
       └───────────┴───────────────┴─────────┘
CONFORMING TO
       POSIX.1-2001.

NOTES
       sem_post() is async-signal-safe: it may be safely called within a signal handler.

4.信号量基本操作

sem_wait (给信号量加锁,信号量--)
1.当信号量大于0的时候,信号量--(类似于pthread_mutex_lock)
2.当信号量小于0的时候,造成线程阻塞.

sem_post (给信号量解锁,信号量++)
将信号量++,同时唤醒则色在信号量上的线程(类比pthread_mutex_unlock)

注意:由于sem_t的实现对用户隐藏,所以所谓的++,--操作只能通过函数来实现,而不能直接用++,--符号.
信号量的处置,决定了占用信号量的线程数的个数.

5.练习

使用信号量完成线程间的同步,模拟生产者消费者问题.如果消费队列中没有数据,消费者不能消费,
只能阻塞等待数据;如果消费队列里盛满了数据,生产者不能生产,只能阻塞等待数据。
/*

作者:Muten
编码时间:20201017
编码目的:演示借助线程中利用信号量模拟【生产者消费者】问题
代码功能:借助信号量模拟【生产者消费者】问题
测试环境:Linux localhost.localdomain 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
版本信息:VER1.0
编译方法:gcc 001.c -lpthread

*/
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>

#define NUM 5

int qunue[NUM];
sem_t blank_number,product_number;
void *producer(void* arg)
{
	int i = 0;
	while(1)
	{
		sem_wait(&blank_number);                     // 生产者这将可用来承载数据的队列长度--,为0则阻塞等待
		qunue[i] = rand() % 1000 +1;                 // 生产一个产品
		printf("-----Producer-----%d.\n",qunue[i]);
		sem_post(&product_number);                   // 生产者将已经承载数据的队列长度++
		i = (i+1)%NUM;                               // 借助下标实现环形                   
		sleep(rand()%1);
	}
}
void *consumer(void * arg)
{
	int i = 0;
	while(1){
		sem_wait(&product_number);                  // 消费者将产品数--,为0则阻塞等待          
		printf("-------------------------Consumer----%d.\n",qunue[i]);
		qunue[i] = 0;                               // 消费掉一个产品
		sem_post(&blank_number);                    // 消费掉一个产品以后将承载数据的队列长度++
		i = (i+1)%NUM;
		sleep(rand()%3);
	}
}

int main()
{
	pthread_t pid ,cid;
	sem_init(&blank_number,0,NUM);
	sem_init(&product_number,0,0);
	pthread_create(&pid,NULL,producer,NULL);
	pthread_create(&cid,NULL,consumer,NULL);
	pthread_join(pid,NULL);
	pthread_join(cid,NULL);
	sem_destroy(&blank_number);
	sem_destroy(&product_number);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值