04进程间通信——信号量

如果有两个或者两个以上的任务 (进程/线程,并发的实体),去访问一个共享资源 (硬件上面的,软件上面的)。我们必须要保证这个共享资源的有序访问,否则会产生不可预知的后果

例子:
    very_important_i = 5; // 共享资源
    
    fun() {
        very_important_i++;
    }
    有两个任务同时调用了fun()这个函数,那么请问very_important_i 最后的值是多少?
            7 ----> 有可能
            6 ----> 有可能,不是我们期望的结果
            8  不可能
            5  不可能

        并发:任务 (进程)在任意时刻都是有可能被暂停的 (分时调度算法,信号)
        出现结果为6,就不是对共享资源的有序访问,是有问题的,所以我们需要对这个共享资源进行某一种方式的保护,以使它有序访问
        “避免竞争”

并发可以提高cpu利用率,有可能造成竞争,造成共享资源的非法访问,程序行为异常....

解决方法:
        能不能不要并发呢?
        显然不可以

在保留并发的前提下,"避免竞争",在访问共享资源时,严格的串行

一. 信号量 (semaphore)机制

(1)信号量是什么?
(2)信号量的作用是什么?
(3)为什么需要信号量?
(4)信号量的工作原理是什么?

        

        信号量 (semaphore)是一种用于提供不同进程或者一个进程内部不同的线程间同步 “避免竞争”的一种机制
        
        进程/线程,任务:并发的实体
        “同步”:并发的实体之间,相互等待,相互制约的一种机制
                       保证实体对共享资源有条件的访问
        
        信号量就是为了保护共享资源,让共享资源有序访问的一种机制
        信号量的目的:为了保护共享资源,使共享资源有序访问
        
        信号量是程序界最高尚的一个东西,因为它不是为了自己而存在的,它是为了别人 (它要保护的对象,共享资源)而存在的,"保镖"

二. 信号量是如何工作的

信号量是为了保护共享资源 (大家都可以访问到的资源)有序的访问

信号量机制其实是程序员之间的一种约定,用来保护共享资源的
        如:进程A和进程B,都需要访问一个共享资源“互斥设备”,那么我们可以使用一个信号量来表示能不能访问这个资源,每一个进程需要访问共享资源前,先去访问该信号量,如果能访问共享资源 (信号量允许),则先把信号量设置为"NO",然后再去访问该共享资源,访问完共享资源,再把信号量设置为“YES”

===========>
        在访问共享资源前,先去判断共享资源是否能被访问?
                LOCK
        如果可以被访问,你就获取了该信号量 (变成“不能访问”),则进入访问共享资源的代码段 (指令段)
        如果不能访问:wait,直到信号量变成“能访问”
        // 访问共享资源的代码   ----- "临界区"
        在访问共享资源后,要把信号量释放 (变成“能被访问”)
                UNLOCK

信号量是如何实现的呢?
        “信号量”大家都可以访问的一个“整数”

一个进程或者线程可以在某一个信号量上面执行如下三种操作:

        1. 创建(create)一个信号量:还要求调用者指定信号量的初始值
                信号量的初始值表示该信号量保护的共享资源,可以同时被多少个任务访问
                sem-->5表示同一时刻可以有5个进程或者线程去同时访问它所保护的共享资源
                sem-->1表示只有一个进程或者线程可以去访问它所保护的资源 “互斥信号量”

        2. 等待(wait)一个信号量
                这个操作会测试这个信号量的值,如果其值<=0那么进程就会等待 (阻塞),一旦其值>0,这个时候,进程就将信号量-1,继续往下执行临界区的代码

                其函数实现类似如下代码:

while (semaphore_value <= 0) {
    sleep; // wait,block这个进程或者线程
}
semaphore_value--; // 表示该进程或者线程获取了该信号量
注意:上面的操作必须是“原子操作”,不允许有两个以及以上的进程同时进入
// ...以下代码,获取了该信号量,就可以访问信号量所保护的资源啦

P操作:proberen(尝试) 荷兰语
down
lock  上锁

        3. 释放一个信号量

                该操作是在访问完共享资源(执行完临界区代码)后要做的一个操作,就是将该信号量的值+1

                其函数实现类似如下代码:

semaphore_value++; // 原子操作
			// 一条语句可能对应多条汇编
			//不能被中断而且只能有一个进程去执行
		
V操作 verhogen(增加) 荷兰语
up
unlock 解锁

信号量去保护共享资源是通过:
        在访问临界区的前面加一个 P操作
        在访问完临界区后加一个 V操作
        来实现的


在遇到“共享资源”在不同的进程或者线程中需要同时被访问的时候,必须要考虑“避免竞争”
        a. 明确谁是共享资源,明确需要包含的对象 (访问共享资源的代码)
        b. 确定临界区 (访问共享资源的代码)
        c. 一个需要包含的共享资源,就需要一个信号量


思考:

       1. 现在有5个独立的共享资源A、B、C、D、E需要保护,程序员决定使用一个信号量S来同时保护这5个资源
        P(S)
        A
        V(S)

        P(S)
        B
        V(S)
        ...
        请问这种设计有什么问题? 该如何改进
        
        虽然可以达到“保护”共享资源的目的,但是降低了并发度,A和B本身可以同时访问

        2. 现在有5个独立的共享资源A、B、C、D、E需要保护,程序员决定使用5个信号量S1、S2、S3、S4、S5来分别保护这5个资源
        S1-->A
        S2-->B
        ...
        
        P(S1)
        A的临界区
        V(S1)
        
        P(S2)
        B的临界区
        V(S2)
        
        ....
        ----------------

        如果一个进程需要同时访问A和B (A和B的临界区)

        P1进程
        P(S1)
        P(S2)
        // 访问A和B的临界区
        V(S2)
        V(S1)
        
        P2进程
        P(S2)
        P(S1)
        // 访问A和B的临界区
        V(S1)
        V(S2)

        
        上面这种设计会造成死锁
        deadlock
        没有外力作用,状态不会改变
        ====>
        P1进程
        P(S1 & S2)
        // 访问A和B的临界区
        V(S1 & S2)


扩展:
1. 死锁到底是什么状态?

        在计算机科学中,死锁(Deadlock)是一个特定的状态,主要出现在多线程或多进程环境中。当两个或更多的进程或线程在执行过程中,因争夺资源而造成一种僵持(互相等待)的现象,若无外力作用,这些进程或线程都将无法向前推进,这种情况就是死锁

2. 死锁产生的条件?

        互斥条件资源不能被共享,即一个资源每次只能被一个进程或线程使用。如果资源可以被多个进程或线程同时访问,那么死锁就不会发生

        请求与保持条件 (占有等待):一个进程或线程因请求资源而阻塞时,对已获得的资源保持不放。这意味着进程或线程在持有部分资源的同时,还在等待其他资源,从而增加了死锁的风险

        不剥夺条件 (不可抢占):进程或线程已获得的资源在未使用完之前不能被剥夺,只能在使用完时由自己释放。这个条件限制了系统强制释放进程或线程持有资源的能力,从而增加了死锁的可能性

        循环等待条件:若干进程或线程之间形成一种头尾相接的循环等待资源关系。这是死锁的直接体现,即每个进程或线程都在等待下一个进程或线程释放资源,而下一个进程或线程又在等待再下一个释放资源,以此类推形成一个闭环

3. 死锁产生的场景?

        系统资源不足:当系统资源不足以满足所有进程或线程的需求时,进程或线程就会因争夺有限的资源而陷入死锁

        进程或线程推进顺序不当:进程或线程的运行推进顺序和速度不同,可能导致资源请求和释放的时机不对,从而引发死锁

        资源分配策略不合理:如果资源分配策略允许进程或线程在持有部分资源的情况下继续申请新资源,而这些新资源又被其他进程或线程持有,就可能形成死锁

三. System V 信号量

System V信号量:随内核的持续性
        计数信号量集 (很多个信号量),计数信号量数组

        计数信号量:该信号量的值可以是大于1的,也就是说该信号量它所保护的共享资源允许多个任务同时访问它
                信号量集:一个信号量集中有多个计数信号量
        
        计数信号量的值为1===>“互斥信号量”
        互斥信号量:该信号量的值要么是1,要么是0。也就是说该信号量它所保护的共享资源最多只有一个任务同时访问它
        
        为什么System V要把信号量,做成一个信号量集呢?
                要满足P(S1 & S2)这种情况
                要么同时获取S1和S2这两个信号量,要么都不获取

 

System V 信号量的大概流程:

 1. 申请一个system V IPC的key    fork
NAME
    ftok - convert a pathname and a project identifier to a System V IPC key
SYNOPSIS
    #include <sys/types.h>
    #include <sys/ipc.h>

ftok的英文可以理解为 file to key
ftok利用一个文件名和一个项目ID(整数)生成一个key
        
key_t ftok(const char *pathname, int proj_id);
       
    pathname:一个文件的路径名(必须存在)
       
    proj_id:一个指定的整数
            如果两个进程要通过system V通信,那么他们的ftok的两个参数必须相同,这样才
可以产生同样的key,从而打开同一个IPC对象
       
    返回值:
        成功返回一个ipc的key,类型是key_t
        失败返回-1,同时errno被设置

key_t key = ftok(PATHNAME, PROJID);
2. 创建或者打开一个System V信号量集        semget
NAME
    semget - get a System V semaphore set identifier
SYNOPSIS
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
		
    key:system V IPC对象的key,一般是ftok的返回值
		 唯一的key对应一个唯一的IPC对象(msg/shm/sem)
		
    nsems:你要创建的信号量集中的信号量的数量
		   如果我们不是创建而是打开一个已经存在的信号量集,此参数可以指定为0,
           一旦创建成功,信号量集中信号量的个数就不能改变啦
	
    semflg:创建标志
			a. 创建信号量集,如果已经存在,则获取并返回;如果不存在,则创建再返回
			    IPC_CREAT | 权限位
			b. 打开
			    0

    返回值:
		成功返回system V信号量集的id(标识一个打开的信号量集)
		失败返回-1,同时errno被设置

The values of the semaphores in a newly created set are indeterminate.
在一个新创建的信号量集中,计数信号量的值是不确定的
所有,我们在新创建一个信号量集后,马上指定他们的初始值(设定为程序员想要的值)

// 创建一个信号量集并且初始化
int sem_id = semget(key, 5, IPC_CREAT | 0664);
3. 控制操作 (设置或者获取信号量集中某一个或者某一些信号量的值)    semctl

初始化信号量只能被设置一次

NAME
    semctl - System V semaphore control operations
SYNOPSIS
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
		
semctl用来对指定的信号量集做cmd指定的操作
    
int semctl(int semid, int semnum, int cmd, args);
	
    semid:要操作的信号量集的id(semget的返回值),标识你要操作的信号量
	
    semnum:要操作信号量集中的哪一个信号量,就是信号量数组的下标,
            从0开始,0,1,2,3,4...nsems-1;
	
	cmd:命令号,常用的有
	         GETVAL:获取第semum个信号量的值,并不是P/V操作

	         SETVAL:设置第semum个信号量的值,并不是P/V操作

	         SETALL:设置信号量集中所有信号量的值

	         GETALL:获取该信号量集中所有信号量的值

	         IPC_RMID:立即删除信号量集合,唤醒所有因调用semop()阻塞在
该信号量集合里的所有进程(相应调用会返回错误且 errno被设置为 EIDRM)
调用进程的有效用户ID必须匹配信号量集合的创建者或所有者,或者调用者必须有特权
参数semnum 被忽略

	         IPC_STAT:获取信号量的属性(semid_ds的结构体信息),把内核中的结构体信
息复制到第四个参数指向的内存空间,这个命令忽略第二个参数

             IPC_SET:把内核中的信号量属性设置为第四个参数指定的结构体的内容
	
    args:针对不同的命令号,第四个参数不一样(属于指定的枚举中的值,需要自己定义)
	          cmd == IPC_RMID 第四个参数不需要,参数semnum 被忽略,一般设置为0
	          如:
	          semctl(semid, 0, IPC_RMID);
	
	          cmd == GETVAL 第四个参数不需要,函数返回值就是要获取到信号量的值
	          如:
	          int val = semctl(semid, 1, GETVAL);
	          获取semid指定的信号量集中第2个信号量的值
	
	          cmd == SETVAL 第四个参数应该是一个int类型的值,表示要设置的信号量的值
	          如:
	          设置semid指定的信号量集中第2个信号量的值为10
	          int sem_val = 10;
	          semctl(semid, 1, SETVAL, sem_val);
	
	          cmd == GETALL 第四个参数应该为一个数组
		      unsigned short vals[];
		      这个数组用来保存获取的每一个信号量的值的
	          如:
		      unsigned short vals[10];
		      int r = semctl(semid, 0, GETALL, vals);
		      vals[0] 保存的是第1个信号量的值
		      vals[1] 保存的是第2个信号量的值
		      ....
	    
             cmd == SETALL 第四个参数应该为一个数组
		     unsigned short vals[];
		     这个数组用来保存需要设置的每一个信号量的值的
	         如:
		     unsigned short vals[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
		     int r = semctl(semid, 0, SETALL, vals);
		     第1个信号量的值就是vals[0] 1
		     第2个信号量的值就是vals[1] 1
		     ....

    On failure semctl() returns -1 with errno indicating the error
	...
	GETVAL      the value of semval.
	...
	All other cmd values return 0 on success.

	返回值:
		根据不同的命令号,semctl的返回值含义不一样
		cmd == GETVAL
		返回值就是表示要获取的那个信号量的值
		....
		
		大部分是成功返回0
		失败都是返回-1,errno被设置
/*
    立即删除信号量集合,唤醒所有因调用semop()阻塞在
    该信号量集合里的所有进程(相应调用会返回错误且 errno被设置为 EIDRM)
    调用进程的有效用户ID必须匹配信号量集合的创建者或所有者,或者调用者必须有特权
    参数semnum 被忽略
*/
semctl(semid, 0, IPC_RMID);

// 获取semid指定的信号量集中第2个信号量的值
int val = semctl(semid, 1, GETVAL);

// 设置semid指定的信号量集中第2个信号量的值为10
int sem_val = 10;
semctl(semid, 1, SETVAL, sem_val);

// 获取信号量集中每个信号量的值,保存到vals数组中
unsigned short vals[10];
int r = semctl(semid, 0, GETALL, vals);

// 设置信号量集中每个信号量的值依次为数组vals中的值
unsigned short vals[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
int r = semctl(semid, 0, SETALL, vals);
4. system V 信号量P / V操作     semop
NAME
    semop, semtimedop - System V semaphore operations
SYNOPSIS
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
		
操作,可能是P操作,也可能是V操作,如何去描述一个操作呢?   结构体
		
在system V的信号量P/V操作,用一个结构体struct sembuf来描述:
	struct sembuf {
	    unsigned short sem_num;
		// 你要进行P/V操作的信号量在信号量集中的编号(下标),对哪一个信号量的操作
        short sem_op;
		// 操作
		    >0 : V操作/up/unlock,释放一个信号量
		    =0 :尝试一下,看是否能够获取信号量
		    <0 : P操作/down/lock,等待一个信号量
		    semval(信号量的值) = 原semval + sem_op
				
        short sem_flg; /* 操作标志 */
		   0 默认,如果进行的是P操作,获取不了,则等待(阻塞)
		   IPC_NOWAIT: 非阻塞,不等待,如果是P操作,能获取则获取,
                       不能获取则立即返回(获取不成功 返回-1)
		   SEM_UNDO:撤销!!!  防止进程带锁退出
		   可以设置多个
		   SEM_UNDO | IPC_NOWAIT

		   如果设置了SEM_UNDO这个标志,内核会额外的记录该进程对该信号量的所有的P/V
操作,然后在该进程退出时,会还原对该信号量的所有操作
		   如:
		   P  V  P  V  kill
		   -1 +1 -1 +1  0
		   
		   P  V  P   kill
		   -1 +1 -1  还原+1
	}
    一个结构体描述对信号量集中一个信号量的操作,具体是P操作还是V操作由sem_op决定
	如果需要同时对多个信号量进行P/V操作,就应该设置多个struct sembuf(P(S1 & S2))
		
int semop(int semid, struct sembuf *sops, unsigned nsops);
		
    semid:要操作的信号量集的ID
	
    sops:struct sembuf的数组
		  表示对信号量集进行的所有操作
		  struct sembuf[0] 表示对信号量集的第0个操作(不是对信号量集中编号为0
          的信号量操作)
	      .....
		  
    nsops:第二个参数sops数组中元素的个数  
           struct sembuf sop;      第二个参数:&sop    第三个参数:1
           struct sembuf sop[3];   第二个参数:sop     第三个参数:3
		
    返回值:
		成功返回0
		出错返回-1,同时errno被设置
		
semop可能会阻塞当前进程/线程,如果是进行P操作,但是获取不了,且IPC_NOWAIT没有被设置,
则会等待
有进程带锁退出了,可能造成“死等”
// (1) 对单个共享资源的访问
P 操作
struct sembuf sop;
sop.sem_num = 0; // 对哪一个信号量操作
sop.sem_op = -1; // P操作
sop.sem_flg = SEM_UNDO; // 防止带锁退出
semop(sem_id, &sop, 1);

// 临界区

V 操作
sop.sem_op = 1; // V 操作
semop(sem_id, &sop, 1);


// (2) 对多个共享资源的访问
P 操作
struct sembuf sop[3];
// 对哪一个信号量操作
sop[0].sem_num = 0;
sop[1].sem_num = 0;
sop[2].sem_num = 0;
// P操作
sop[0].sem_op = -1;
sop[1].sem_op = -1;
sop[2].sem_op = -1;
sop.sem_flg = SEM_UNDO; // 防止带锁退出
int ret = semop(sem_id, sop, 3);

// 临界区

V 操作
sop[0].sem_op = 1;
sop[1].sem_op = 1;
sop[2].sem_op = 1;
ret = semop(sem_id, sop, 3);
semtimedop:限时等待.规定时间内没有完成操作,则返回-1
		
使用第四个参数timeout描述等待时间
       
int semtimedop(int semid, struct sembuf *sops, unsigned nsops, 
                                    struct timespec *timeout);	
		
    前面三个参数都是和semop一样的
		
	struct timespec {
	    time_t tv_sec; // 秒数
		long tv_nsec; // 纳秒
	};
		struct timespec
		有两个成员,一个是秒,一个是纳秒, 所以最高精确度是纳秒
		1s = 1000 ms
		1ms = 1000 us
		1us = 1000 ns
		
		例子:
		最多等待5秒
		struct timespec tv;
		tv.tv_sec = 5;
		tv.tv_nsec = 0;
		
		semtimedop(semid, sops, nsops, &tv);
​​​​​​​

四. 使用system V 信号量解决20W问题

使用system V信号量去保护共享资源   20W问题

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SHMPATHNAME  "/home/china/SC"  // 不能写成 "~/SC"
#define PROJ_ID 250
#define SIZE 4096

#define SEMPATHNAME "/home/china"

void my_handler(int sig) {
	printf("get a signal\n");
}

int main(int argc, char *argv[]) {

	signal(SIGUSR1, my_handler);

	// 1.申请一个system v IPC的key 用于共享内存
	key_t shm_key = ftok(SHMPATHNAME, PROJ_ID);
	if (-1 == shm_key) {
		perror("ftok error");
		return -1;
	}
	
	// 申请一个system v IPC的key 用于信号量
	key_t sem_key = ftok(SEMPATHNAME, PROJ_ID);
	if (-1 == sem_key) {
		perror("ftok error");
		return -1;
	}

	// 2.创建或者打开system v 的共享内存
	int shm_id = shmget(shm_key, SIZE, IPC_CREAT | 0664);
	if (-1 == shm_id) {
		perror("shmget error");
		return -1;
	}
	
	// 3. 映射
	int *p = (int *)shmat(shm_id, NULL, 0);
	if (p == NULL) {
		perror("shmat error");
		// 删除共享内存
		shmctl(shm_id, IPC_RMID, NULL);
		return -1;
	}

	printf("shmat success\n");

	// 4. 操作(通过指针p操作共享内存区域)
	*p = 0;
	int i = 100000;

	// 创建或者打开一个System V信号量集  
	int sem_id = semget(sem_key, 5, IPC_CREAT | 0664);
	if (sem_id == -1) {
		perror("semget failed");
		goto ERR;
	}

	// 初始化信号量
	// 只能设置一次,why?
	unsigned short vals[5] = {1, 1, 1, 1, 1};
	int r = semctl(sem_id, 0, SETALL, vals);
	if (r == -1) {
		perror("semctl error");
		semctl(sem_id, 0, IPC_RMID); // 删除信号量集
		goto ERR;
	}

	// 等待
	pause();
	
	while (i--) {
		// P操作
		struct sembuf sop;
		sop.sem_num = 0; // 对哪一个信号量操作
		sop.sem_op = -1; // P操作
		sop.sem_flg = SEM_UNDO; // 防止带锁退出
		semop(sem_id, &sop, 1);

		(*p)++;
		printf("*p = %d\n", *p);

		// V操作
		sop.sem_op = 1; // V 操作
		semop(sem_id, &sop, 1);
	}

	semctl(sem_id, 0, IPC_RMID); // 删除信号量集
	
ERR:
	// 5. 解映射
	shmdt(p);
	// 6. 删除共享内存
	shmctl(shm_id, IPC_RMID, NULL);

	return 0;
}
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SHMPATHNAME  "/home/china/SC"  // 不能写成 "~/SC"
#define PROJ_ID 250
#define  SIZE 4096

#define SEMPATHNAME "/home/china"

int main(int argc, char *argv[]) {

	// 1.申请一个system v IPC的key 用于共享内存
	key_t shm_key = ftok(SHMPATHNAME, PROJ_ID);
	if (-1 == shm_key) {
		perror("ftok error");
		return -1;
	}
	
	// 申请一个system v IPC的key 用于信号量
	key_t sem_key = ftok(SEMPATHNAME, PROJ_ID);
	if (-1 == sem_key) {
		perror("ftok error");
		return -1;
	}

	// 2.创建或者打开system v 的共享内存
	int shm_id = shmget(shm_key, SIZE, IPC_CREAT | 0664);
	if (-1 == shm_id) {
		perror("shmget error");
		return -1;
	}
	
	// 3. 映射
	int *p = (int *)shmat(shm_id, NULL, 0);
	if (p == NULL) {
		perror("shmat error");
		// 删除共享内存
		shmctl(shm_id, IPC_RMID, NULL);
		return -1;
	}

	printf("shmat success\n");

	// 4. 操作(通过指针p操作共享内存区域)
	int i = 100000;

	// 创建或者打开一个System V信号量集
	int sem_id = semget(sem_key, 5, IPC_CREAT | 0664);
	if (sem_id == -1) {
		perror("semget failed");
		goto ERR;
	}

	kill(35044, SIGUSR1); // ps -ef  获取p1进程的进程号
	
	while (i--) {
		// P操作
		struct sembuf sop;
		sop.sem_num = 0; // 对哪一个信号量操作
		sop.sem_op = -1; // P操作
		sop.sem_flg = SEM_UNDO; // 防止带锁退出
		semop(sem_id, &sop, 1);

		(*p)++;
		printf("*p = %d\n", *p);

		// V操作
		sop.sem_op = 1; // V 操作
		semop(sem_id, &sop, 1);
	}

	semctl(sem_id, 0, IPC_RMID); // 删除信号量集
	
ERR:
	// 5. 解映射
	shmdt(p);
	// 6. 删除共享内存
	shmctl(shm_id, IPC_RMID, NULL);

	return 0;
}

五. POSIX 信号量  ——> 单个信号量

System V信号量 是信号量集
一个信号量集中可以有多个信号量


POSIX 信号量 是单个信号量


有名信号量:可以用于进程间或者线程间 同步/互斥,在文件系统中有一个入口 (有一个文件名,inode),但是信号量对象 (值)却是存在于内核中
        =======>
        既可以用于任意进程间同步,也可以用于线程间的同步

无名信号量:也可以用于进程间或者线程间 同步/互斥
        没有名字,无名信号量存在于内存中,“基于内存的信号量”
        如果信号量存在的这一段内存在一个“内核的共享内存中”,任意的进程都可以访问,线程也可以访问
        =======>
        既可以用于任意进程间同步,也可以用于线程间的同步

        如果信号量存在的这段内存在一个进程的地址空间,此时只能用于该进程内部所有线程的同步/互斥  (进程的地址空间是独立的)

POSIX 信号量的API函数接口:POSIX信号量使用sem_t来描述

1. 创建或者打开一个POSIX信号量

        POSIX信号量使用sem_t来表示,不管是有名信号量还是无名信号量,都是使用sem_t类型表示

有名信号量的创建 (sem_open)

NAME
    sem_open - initialize and open a named semaphore
SYNOPSIS
    #include <fcntl.h>
    #include <sys/stat.h>
    #include <semaphore.h>
      
sem_open用来初始化并且打开一个有名信号量
       
sem_t *sem_open(const char *name, int oflag);
       
sem_t *sem_open(const char *name, int oflag,
                 mode_t mode, unsigned int value);
       
    name:要创建或者打开的POSIX有名信号量在文件系统中的路径名
          (以‘/’开头的路径,路径名中只有一个'/',如:/test.sem)
       
    oflag:
        a. 单纯的打开 0
        b. 创建O_CREAT
        第三个参数和第四个参数当你是创建一个有名信号量的时候,才需要
       
    mode:创建的权限位,有两种方式指定
        S_IRUSR ...
        0664
       
    value:指定创建的有名信号量的初始值,信号量保护的共享资源
           可以同时有几个进程/线程访问

    返回值:
        成功返回一个sem_t的指针,指向POSIX创建的有名信号量,有可能是在内核分配一块
空间,并且返回首地址
        失败返回一个SEM_FAILED的宏,同时errno被设置

Link with -pthread
使用有名信号量必须链接多线程库
编译的时候加上 -lpthread
libpthread.so // 多线程库

有名信号量直接创建在共享内存区域
/dev/shm 有你创建的有名信号量

无名信号量的创建 (sem_init)
    a. 定义或者分配一个无名信号量 sem_t 的变量
        1. sem_t sem;
        2. sem_t *psem = (sem_t *)malloc(sizeof(sem_t));
            用于进程内部的线程间的通信,隶属于进程的地址空间,其他的进程不能访问,但是一个进程的所有线程共享一个进程的地址空间,所以一个进程的所有线程都可以访问
        
        3. int shm_id = shmget(key, sizeof(sem_t), IPC_CREAT | 0664);
// 创建或者打开system V共享内存对象
            sem_t *psem = (sem_t *)shmat(shm_id,NULL, 0); // 映射
            无名信号量存在于内核中 (共享内存中),可以用于任意进程间的同步/互斥
         
        三种方式都是分配一个保存信号量的空间
        
    b. 初始化无名信号量 (sem_init)
        无名信号量是基于内存的信号量,a步骤中的1、2信号量在进程的地址空间中,3是存在于内核中
        已经有一块空间,只是把信号量保存指定的空间中去

NAME
    sem_init - initialize an unnamed semaphore
SYNOPSIS
    #include <semaphore.h>
        
sem_init用来初始化一个无名信号量
       
int sem_init(sem_t *sem, int pshared, unsigned int value);
        
    sem:指针,指向你要初始化的无名信号量
        
    pshared:该无名信号量的共享方式
            0:进程内部的线程共享
                sem指向的地址是进程内部的地址
            1:不同进程间共享(不同的进程都可以访问信号量,
                sem指向的地址是内核的共享内存区域)
    
    value:初始化的信号量的值
    
    Link with -pthread.
       
    返回值:
        成功返回0
        失败返回-1,同时errno被设置
        
注意:信号量只应该被初始化一次,否则可能导致不可预知的行为    
2. 对POSIX信号量的操作

有名信号量和无名信号量的操作接口是一样的


P操作

NAME
    sem_wait, sem_timedwait, sem_trywait - lock a semaphore
SYNOPSIS
    #include <semaphore.h>
		
sem_wait:等待一个信号量/获取一个信号量
		  阻塞等待,如果不能获取则阻塞
          lock,sem指向的信号量值减1

int sem_wait(sem_t *sem);
		
    sem:指针,指向要操作的信号量
		
    返回值:
		返回0,表示获取了该信号量
		返回-1,表示出错,同时errno被设置
		
sem_trywait 是 sem_wait的不阻塞版本,能获取则获取
不能获取则立即返回(错误,设置相应的错误码)
       
int sem_trywait(sem_t *sem);
	
    sem:指针,指向要操作的信号量
		
    返回值:
		返回0,表示获取了该信号量
		返回-1,表示出错,同时errno被设置
sem_timedwait:限时等待,规定时间内没有完成操作,则返回-1
				
abs_timeout:绝对时间,指定等待的时间
		
固定等到一个时间点(系统时间点),如果没有获取则立即返回
		
绝对时间 = 当前时间(1970年1月1日到现在的秒数) + 要等待的时间
		
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
		
    sem:指针,指向要操作的信号量
	
    abs_timeout:
		struct timespec {
            time_t tv_sec;      /* 秒 Seconds */
            long   tv_nsec;     /* 纳秒 Nanoseconds [0 .. 999999999] */
       };

Link with -pthread.	

例子:
    clock_gettime()
        // 获取当前时间(秒数),保存到tp指向的结构体中
        // Link with -lrt  librt.so
    
    #include <time.h>

    int clock_gettime(clockid_t clk_id, struct timespec *tp); 
		
    struct timespec ts;
	clock_gettime(CLOCK_REALTIME, &ts);
	// 如果需要等待5秒 30 ms
	ts.tv_sec += 5;
	ts.tv_nsec += 30000000;
	if (ts.tv_nsec >= 1000000000) {
		ts.tv_sec++;
		ts.tv_nsec -= 1000000000;
	}
	// ts描述一个未来的绝对时间
		
	int r = sem_timedwait(sem, &ts);
	// 和system V的时间是有非常大的区别的

V操作

NAME
    sem_post - unlock a semaphore
    // 解锁一个信号量,使信号量的值递增
SYNOPSIS
    #include <semaphore.h>

int sem_post(sem_t *sem);
		
sem_post是用来释放sem指向的POSIX信号量的
		
    返回值:
		成功返回0
		失败返回-1,同时errno被设置
		
Link with -pthread.

其他操作:获取信号量的值

NAME
    sem_getvalue - get the value of a semaphore
SYNOPSIS
    #include <semaphore.h>
		
sem_getvalue用来获取指定的信号量的值
       
int sem_getvalue(sem_t *sem, int *sval);
		
    sem:指针,指向你要获取的信号量
		
    sval:指针,指向一块可用的内存空间,保存获取到的信号量的值
		
    返回值:
		成功返回0
		失败返回-1,同时errno被设置
       
Link with -pthread.	
3. 删除或者关闭一个信号量

有名信号量
        sem_close 用来关闭一个POSIX有名信号量
        sem_unlink 用来删除一个POSIX有名信号量

NAME
    sem_close - close a named semaphore
SYNOPSIS
    #include <semaphore.h>
		
sem_close用来关闭sem指向的有名信号量
       
int sem_close(sem_t *sem);
		
    返回值:
		成功返回0
		失败返回-1,同时errno被设置
        
Link with -pthread.			
------------------------------------------------
NAME
    sem_unlink - remove a named semaphore
SYNOPSIS
    #include <semaphore.h>

int sem_unlink(const char *name);
	   
    name:要删除的POSIX有名信号量的路径名
		
    返回值:
		成功返回0
		失败返回-1,同时errno被设置
      
Link with -pthread.

无名信号量

NAME
    sem_destroy - destroy an unnamed semaphore
SYNOPSIS
    #include <semaphore.h>
      
int sem_destroy(sem_t *sem);
		
    sem:指向你要删除的无名信号量
		
	返回值:
		成功返回0
		失败返回-1,同时errno被设置
       
Link with -pthread

六. POSIX 信号量解决20W问题

1. POSIX 有名信号量

2. POSIX 无名信号量 ---->内存中

3. POSIX 无名信号量 ----->内核中

​​​​​​​

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值