C++使用信号量实现共享内存的互斥访问

自己实现一个信号量操作的类【可以直接使用】

头文件

#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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值