代码000:叮铃铃,摇晃的铃铛。PV操作,生产者,消费者。
int semget(key_t key,int nsems,int semflg);//得到关键做名字,须定个数许定权。
西姆拿着钥匙,可以打开很多个数门,但是有的门上有几把锁,有多把锁的有的可以打开,有的打不开。
int semctl(int semid,int semnum,int cmd,union semun);//控制标志数定位,十命不同改枚举。枚举四值值为首,信号结构排第二。多个数值用数组,信号信息存最后。命令图像:号角
她冲进指定的房间,吹响命令的号角,打开装着梅子的包。
int semop (int semid,struct sembuf *sops,unsigned nsops);//操作序号要指定,缓存结构须写明,最后一个可写0。
她拿着钥匙缓了口气,吃了个蛋。
缓冲结构要定位,排行第几要说清,信号量值变多少,sem_op来说明,最后一个标志位,阻塞撤销可指定。
union semun //尺量结构,组长缓冲。
{
int val; //值 -- 瓦妞
struct semid_ds* buf;//信号量结构(semid_ds data structure)
unsigned short* array;//值数组
struct seminfo *buf;//IPC_INFO缓存
}
struct semid_ds //拳打OC(日月)数个数。
{
struct ipc_perm sem_perm;//权限
time_t sem_otime;// 记录最后一次执行PV操作的时间(调用semop)
time_t sem_ctime;//信号量集最后一次改变的时间(调用semctl)
unsigned long sem_nsems;//信号量集中信号量的个数
};
struct sembuf
{
short sem_num;//要操作的第几个信号量
short sem_op;//是P操作还是V操作
short sem_flg;//可以是IPC_NOWAIT,SEM_UNDO,0
};
============================================================================
信号量是由迪杰斯特拉提出来的,没错就是那个搞算法的家伙。
PV操作
提到信号量,不得不说PV操作:
信号量的PV操作:
P(s)
{
s.value--; //消耗资源?
if(s.value<0)
{
将当前进程置位等待状态,
并插入等待队列s.queue;
}
}
V(s)
{
s.value++;//归还资源
if(s.value <=0)
{
唤醒相应等待的队列s.queue中的一个进程,
将其改变为就绪态,
并将其插入就绪队列。
}
}
生产者与消费者示例:
semaphore full=0;//有多少可用资源
semaphore empty=BUFFER_SIZE;//资源放置容器,满后不能生产资源
semaphore mutex=1;//独占锁
Producer(item)
{
P(empty);
P(mutex);
生产资源
V(mutex);
V(full);
}
Consumer()
{
P(full);
P(mutex);
消耗资源
V(mutex);
V(empty);
}
信号量的PV操作:
在同一个进程中主要是为了解决资源互斥的问题,在不同的进程中时为了解决同步的问题。
信号量的含义:
S>0:S表示可以使用的资源的数量。
S=0;表示无可用资源,也无等待进程。
S<0;S的绝对值就是表示在等待资源的进程数量。
信号量函数
Int semget(key_t key,int nsems,int semflg);
Key:信号集的名字
Nsems:信号集中信号量的个数
Semflg:由九个权限标志构成
返回值:失败返回-1,成功返回信号量集标志码(这个标志码是怎么来的?)
Semctl函数用于控制信号量集:
Int semctl(int semid,int semnum,int cmd,…);
参数:
Semid:由semget返回的信号量的标志码,semget返回值
Semnum:信号量集中的信号量的数量
Cmd:将要采取的动作:(有10个可以取的值)
命令 | 解 释 |
IPC_STAT | 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中 |
IPC_SET | 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值 |
IPC_RMID | 从内核中删除信号量集合 |
GETALL | 从信号量集合中获得所有信号量的值,并把其整数值存到semun联合体成员的一个指针数组中 |
GETNCNT | 返回当前等待资源的进程个数 |
GETPID | 返回最后一个执行系统调用semop()进程的PID |
GETVAL | 返回信号量集合内单个信号量的值 |
GETZCNT | 返回当前等待100%资源利用的进程个数 |
SETALL | 与GETALL正好相反 |
SETVAL | 用联合体中val成员的值设置信号量集合中单个信号量的值 |
…:最后一个参数根据命令不同而不同。
传入的结构体为:
union semun
{
int val; /* Value for SETVAL 最大为32767*/
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO*/
};
struct semid_ds
{
struct ipc_perm sem_perm;
time_t sem_otime;// 记录最后一次执行PV操作的时间
time_t sem_ctime;//信号量集最后一次改变的时间
unsigned long sem_nsems;//信号量集中信号量的个数
};
返回值:成功0,失败-1;
semop函数:用来创建和访问一个信号量集,这个就是进行PV操作的。
semop (int semid,struct sembuf *sops,unsigned nsops);
semid:由semget返回的信号量的标志码,semget返回值
sops 是一个指向一个结构数值struct sembuf的指针
nsops:信号量的个数
返回值:0成功,-1失败。
这个函数可以传入一个枚举变量:
struct sembuf
{
short sem_num;//要操作的第几个信号量
short sem_op;//是P操作还是V操作
short sem_flg;//可以是IPC_NOWAIT,SEM_UNDO,0
};
这个结构体可以在信号量上执行各种操作:
在一个信号量上可以
执行各种操作(即系统调用),包括:
1.将信号量设置成一个绝对值;
2.在信号量当前值的基础上加上一个数量;
3.在信号量当前值的基础上减去一个数量;
4.等待信号量的值等于 0;
后两个可能会导致调用进程阻塞,如果使用参数IPC_NOWAIT的话就直接返回失败。
使用SEM_UNDO会在进程结束的时候撤销PV操作。(这里有坑,如果在执行semop后又使用semctl设置了信号量的值呢?)
在多个同级进程中,如果使用信号量,一般是先semget创建,semctl初始化,再之后各个进程semop,那么这样就会形成一个竞态。
可以使用semid_ds 数据结构中的 sem_otime 字段的初始化。在一个信号量集首次被创建时, sem_otime
字段会被初始化为 0,并且只有后续的 semop()调用才会修改这个字段的值。因此可以利用这
个特性来消除上面描述的竞争条件。
还有多个信号量之间发生阻塞时,会使用银行家算法来避免死锁问题。
下面的例子是使用信号量作为互斥锁:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h>
#include <stdio.h>
#include <thread>
union semun
{
int val; /* Value for SETVAL */ // 这个才是要设置的值
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO*/
};
class sem_lock{
public:
sem_lock():m_semid(-1){};
int sem_create(key_t key);
int sem_setval(int val);
int sem_getmode();
int sem_getval();
int sem_p();
int sem_v();
~sem_lock()
{
semctl(m_semid, 0, IPC_RMID);
}
private:
int m_semid;
};
int sem_lock::sem_getval()
{
int ret;
ret=semctl(m_semid,0,GETVAL,0);//semid表示信号量标志码,0:对信号量集中第0个信号量进行操作,GETVAL:表示要设置值,直接返回信号的值。
if(ret==-1)
{
perror("sem_getval failed");
}
return ret;
}
int sem_lock::sem_create(key_t key)
{
m_semid=semget(key,1,IPC_CREAT|IPC_EXCL|0666);
if(m_semid==-1)
{
perror("sem_create failed");
}
return m_semid;
}
int sem_lock::sem_setval(int val)
{
union semun su;
su.val=val;
int ret;
ret=semctl(m_semid,0,SETVAL,su);//semid表示信号量标志码,0:对信号量集中第0个信号量进行操作,SETVAL:表示要设置值,su:设置的值。
if(ret==-1)
{
perror("sem_setval failed");
}
return ret;
}
int sem_lock::sem_p()
{
struct sembuf sops={0,-1,0};//0:对信号量集中的第一个信号量进行操作,-1:P操作,
int ret;
ret = semop(m_semid,&sops,1);//第二个参数可以指向一个数组,就是可以对多个信号量进行操作。第三个参数是表示只操作一个信号量
if(ret==-1)
{
perror("sem_p failed");
}
return ret;
}
int sem_lock::sem_v()
{
struct sembuf sops={0,1,0};//0:对信号量集中的第一个信号量进行操作,1:V操作,
int ret;
ret = semop(m_semid,&sops,1);//第二个参数可以指向一个数组,就是可以对多个信号量进行操作。第三个参数是表示只操作一个信号量
if(ret==-1)
{
perror("sem_v failed");
}
return ret;
}
int sem_lock::sem_getmode()
{
union semun su;
struct semid_ds sem;
su.buf=&sem;
int ret =semctl(m_semid,0,IPC_STAT,su);
if(ret == -1)
{
perror("sem_getmode failed");
}
printf("permissions: %o",su.buf->sem_perm.mode);
}
int g_res=99999;
int g_first=0;
int g_second=0;
int g_semid=0;
sem_lock lock;
void proc_first()
{
while(g_res>0)
{
lock.sem_p();
g_res?--g_res,++g_first:0;
lock.sem_v();
}
}
void proc_second()
{
while(g_res>0)
{
lock.sem_p();
g_res?--g_res,++g_second:0;
lock.sem_v();
}
}
int main()
{
lock.sem_create(1234);
lock.sem_setval(1);
std::thread first(proc_first);
std::thread second(proc_second);
first.join();
second.join();
printf("%d\n",g_first+g_second);
return 0;
}
编译输出:
g++ -std=c++11 sem.cpp -o sem -lpthread
./sem
99999
===============助记
1.semget-广场:有名字,大小,权限(是否公开)
2.semctl:天空:领空的名字,控制哪个烟花,何时炸(将要采取的动作),炸包含的信息
union semun
{
int val; //值
struct semid_ds* buf;//信号量结构(semid_ds data structure)
unsigned short* array;//值数组
struct seminfo *buf;//IPC_INFO缓存
}
尺量结构,组长缓冲。
信号量结构:
struct semid_ds
{
struct ipc_perm sem_perm;//权限
time_t sem_otime;// 记录最后一次执行PV操作的时间(调用semop)
time_t sem_ctime;//信号量集最后一次改变的时间(调用semctl)
unsigned long sem_nsems;//信号量集中信号量的个数
};
拳打OC(日月)数个数。
3.semop (int semid,struct sembuf *sops,unsigned nsops);
看烟花的人:
看哪个烟花,
struct sembuf
{
short sem_num;//要操作的第几个信号量
short sem_op;//是P操作还是V操作
short sem_flg;//可以是IPC_NOWAIT,SEM_UNDO,0
};
定位PV方式:
看烟花要定位看哪个,人多的地方看还是人少的地方看,有人挡着怎么办(是否等待锁)?