自己实现一个信号量操作的类【可以直接使用】
头文件
#ifndef __CSEMP_HH__
#define __CSEMP_HH__
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 信号量
class Csemp
{
private:
union semun // 用于信号量操作的共同体
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
int m_semid; // 信号量id(文件描述符)
// 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况,
// 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢复为初始值。
// 如果信号量用于互斥锁,设置为SEM_UNDO。
// 如果信号量用于生产消费者模型,设置为0。
short m_seg_flg;
Csemp(const Csemp &) = delete;
Csemp &operator=(const Csemp &) = delete;
public:
Csemp() : m_semid(-1) {}
// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
// 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
// 如果用于生产消费者模型,value填0,sem_flg填0。
bool init(int key,unsigned short value = 1,short seg_flg = SEM_UNDO);
bool wait(short value = -1); // 信号量的P操作,如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
bool post(short value = 1); // 信号量的V操作。
int getvalue(); // 获取信号量的值,成功返回信号量的值,失败返回-1。
bool destory(); // 销毁信号量。
};
#endif
实现文件
#include "Csemp.hh"
#include <cstdio>
#include <cerrno>
// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
// 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
// 如果用于生产消费者模型,value填0,sem_flg填0。
bool Csemp::init(int key, unsigned short value, short seg_flg)
{
if (m_semid != -1)
return false; // 说明已经初始化了
m_seg_flg = seg_flg;
// 信号量的初始化分三个步骤:
// 1)获取信号量,如果成功,函数返回。
// 2)如果失败,则创建信号量。
// 3) 设置信号量的初始值。
// 1)获取信号量
if ((m_semid = semget(key, 1, 0666)) == -1)
{
// 信号量不存在,则创建信号量
if (errno == ENOENT)
{
// 设置为IPC_EXCL与IPC_CREAT一起使用时,如果内核中已存在键值与key相等的信号量集,则会报错
// 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。
if ((m_semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL)) == -1)
{
// 信号量创建失败
if (errno == EEXIST)
{
// 如果错误代码是信号量已存在,则再次获取信号量。
if ((m_semid = semget(key, 1, 0666)) == -1)
{
// 表示获取失败
perror("init 1 semget()...\n");
return false;
}
else
{
return true;
}
}
else
{
// 表示其他错误
perror("init 2 semget()...\n");
return false;
}
}
else
{
// 信号量创建成功【如下是设置信号量的初始】
union semun sem_union;
sem_union.val = value; // 设置信号量的初始值
if (semctl(m_semid, 0, SETVAL, sem_union) < 0)
{
perror("init semctl()...\n");
return false;
}
}
}
else
{
// 信号量存在
perror("init 3 semget()...\n");
return false;
}
}
// 信号量存在,直接返回true
return true;
}
// 信号量的P操作(把信号量的值加value,value为负数,实际上是减abs(value)),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
bool Csemp::wait(short value)
{
// 表示信号量还未进行初始化
if (m_semid == -1)
return false;
struct sembuf sem_b;
sem_b.sem_num = 0; // 信号量标号,0代表第一个信号量
// 这里的sem_b.sem_op = value虽然是赋值操作,但是实际上是信号量值+了value,但value是负数,相当于做了减法
sem_b.sem_op = value; // P操作的value必须小于0
sem_b.sem_flg = m_seg_flg;
if (semop(m_semid, &sem_b, 1) == -1)
{
perror("V semop()");
return false;
}
return true;
}
// 信号量的V操作(把信号量的值减value)。
bool Csemp::post(short value)
{
if (m_semid == -1)
return false;
struct sembuf sem_b;
sem_b.sem_num = 0; // 信号量编号,0代表第一个信号量。
// 这里的sem_b.sem_op = value虽然是赋值操作,但是实际上是信号量值+了value
sem_b.sem_op = value; // V操作的value必须大于0
sem_b.sem_flg = m_seg_flg;
if (semop(m_semid, &sem_b, 1) == -1)
{
perror("V semop()");
return false;
}
return true;
}
// 获取信号量的值,成功返回信号量的值,失败返回-1。
int Csemp::getvalue()
{
return semctl(m_semid, 0, GETVAL);
}
bool Csemp::destory()
{
if (m_semid == -1)
return false;
if (semctl(m_semid, 0, IPC_RMID) == -1)
{
perror("destroy semctl()");
return false;
}
else
{
// 销毁成功
return true;
}
}
使用的流程:① 创建信号量对象 ② 初始化信号量 ③ 调用wait()函数加锁 ④ 调用post()函数解锁
1、创建共享内存的结构体
struct stgirl
{
int no;
char name[20];
};
一共分为一下几个部分
第1步:创建/获取共享内存,键值key为0x5005,也可以用其它的值。
第2步:把共享内存连接到当前进程的地址空间。
第3步:创建二元信号量【互斥信号量】
第4步:初始化二元信号量
第5步:访问共享内存
// 互斥锁
void test3(int no, const char *name)
{
// 第1步:创建/获取共享内存,键值key为0x5005,也可以用其它的值。
// IPC_CREAT表示如果共享内存段不存在,则创建它
int shmid = shmget(0x5005, sizeof(stgirl), 0640 | IPC_CREAT);
if (shmid == -1)
{
cout << "共享内存创建失败...\n";
return;
}
cout << "shmid:" << shmid << "\n";
// 第2步:把共享内存连接到当前进程的地址空间。
// shmat失败返回:(void *)-1
// 后两个参数填0,表示系统会默认选择
stgirl *ptr = (stgirl *)shmat(shmid, 0, 0);
if (ptr == (void *)-1)
{
cout << "shmat() failed...\n";
return;
}
// 3、创建二元信号量【互斥信号量】
Csemp mutex;
// 4、初始化二元信号量
// 表示0x5005这个名字的信号量已经被使用
if (mutex.init(0x5005) == false)
{
cout << "mutex.init(0x5005) failed.\n";
return;
}
// 申请加锁
cout << "申请加锁\n";
mutex.wait();
cout << "加锁成功\n";
// 使用共享内存,对共享内存进行读/写
cout << "原值:no=" << ptr->no << ",name=" << ptr->name << endl; // 显示共享内存中的原值。
ptr->no = no;
strcpy(ptr->name, name);
cout << "新值:no=" << ptr->no << ",name=" << ptr->name << endl; // 显示共享内存中的原值。
sleep(5); // 模拟操作共享内存需要5秒
mutex.post(); // 解锁
cout << "解锁...\n";
// 将共享内存从当前进程中分离
shmdt(ptr);
// 删共享内存
/* if(shmctl(shmid,IPC_RMID,0) == 1)
{
cout << "共享内存删除失败...\n";
return;
}
else
{
cout << "删除成功...\n";
} */
}
int main(int argc, char const *argv[])
{
if (argc != 3)
{
cout << "Using:./demo no name\n";
return -1;
}
test3(stoi(string(argv[1])), argv[2]);
return 0;
}
运行流程:调用main函数时候,携带两个参数,param1:数字;param2:name