Linux 信号量

信号量的产生原因

我们将可能会被多个执行流同时访问的资源叫做临界资源,临界资源需要进行保护否则会出现数据不一致等问题。

当我们仅用一个互斥锁对临界资源进行保护时,相当于我们将这块临界资源看作一个整体,同一时刻只允许一个执行流对这块临界资源进行访问。

但实际我们可以将这块临界资源再分割为多个区域,当多个执行流需要访问临界资源时,如果这些执行流访问的是临界资源的不同区域 ,那么我们**可以让这些执行流同时访问临界资源的不同区域,**此时不会出现数据不一致等问题。

举个栗子:

假设临界资源是一块数组:

cpp 复制代码
vector<int>nums(5,1)

互斥锁的要求是只能访问这个数组nums,信息量可以允许A执行流访问nums[1];B执行流访问nums[2],只要下标是不一样的,其他执行流都可以访问数组nums。

信号量的概念

信号量又叫做信号灯,其本质是一个支持PV操作的计数器。POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

信号量函数

初始化

cpp 复制代码
SEM_INIT(3)                     Linux Programmer's Manual                    SEM_INIT(3)

NAME
       sem_init - initialize an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);

       Link with -pthread.

参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁

cpp 复制代码
NAME
       sem_destroy - destroy an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_destroy(sem_t *sem);

       Link with -pthread.

等待

cpp 复制代码
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

功能:等待信号量,会将信号量的值减1

发布

cpp 复制代码
NAME
       sem_post - unlock a semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_post(sem_t *sem);

       Link with -pthread.

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。

二元信号量模拟实现互斥功能

信号量本质是一个计数器,如果将信号量的初始值设置为1,那么此时该信号量叫做二元信号量。

基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性


环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来 判断满或者空。另外也可以预留一个空的位置,作为满的状态

生产者和消费者申请和释放资源

空间资源和数据资源

代码上用 data_sem和space_sem分别表示数据资源和空间资源

生产者:生产者关注的是空间资源,只要环形队列中有空间,他就可以生产。

=》生产者申请空间资源,释放数据资源

让我们来分析一下生产者具体需要做什么:

1.如果blank_sem的值不为0,则信号量申请成功,此时生产者可以进行生产操作。

2.如果blank_sem的值为0,则信号量申请失败,此时生产者需要在blank_sem的等待队列下进行阻塞等待,直到环形队列当中有新的空间后再被唤醒

消费者:消费者关注的是数据资源,只要有数据就能消费

=》消费者申请数据资源,释放空间资源

消费者有具体怎么做呢?

虽然生产者在进行生产前是对blank_sem进行的P操作,但是当生产者生产完数据,应该对data_sem进行V操作而不是blank_sem。
生产者在生产数据前申请到的是blank位置,当生产者生产完数据后,该位置当中存储的是生产者生产的数据,在该数据被消费者消费之前,该位置不再是blank位置,而应该是data位置。
当生产者生产完数据后,意味着环形队列当中多了一个data位置,因此我们应该对data_sem进行V操作。

必须遵守的两个规则

第一个规则:生产者和消费者不能对同一个位置进行访问。(这个是显然的,如果同时访问同一个位置的数据,可能会产生意外的错误)

第二个规则:无论是生产者还是消费者,都不应该将对方套一个圈以上。

(我们通过信号量适当地让消费者线程和生产者线程相互切换)

信号量保护环形队列的原理

代码实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
const int NUM=16;
class RingQueue
{
public:
    RingQueue(int _cap = NUM) : q(_cap), cap(_cap)
    {
        sem_init(&data_sem, 0, 0);
        sem_init(&space_sem, 0, cap);
        consume_step = 0;
        product_step = 0;
    }
    void PutData(const int &data)
    {
        sem_wait(&space_sem); // P
        q[consume_step] = data;
        consume_step++;
        consume_step %= cap;
        sem_post(&data_sem); // V
    }
    void GetData(int &data)
    {
        sem_wait(&data_sem);
        data = q[product_step];
        product_step++;
        product_step %= cap;
        sem_post(&space_sem);
    }
    ~RingQueue()
    {
        sem_destroy(&data_sem);
        sem_destroy(&space_sem);
    }

private:
    std::vector<int> q;
    int cap;
    sem_t data_sem;
    sem_t space_sem;
    int consume_step;
    int product_step;
};
void *consumer(void *arg)
{
    RingQueue *rqp = (RingQueue *)arg;
    int data;
    while (true)
    {
        rqp->GetData(data);
        std::cout << "Consume data done : " << data << std::endl;
        sleep(1);
    }
}

void *producter(void *arg)
{
    RingQueue *rqp = (RingQueue *)arg;
    srand((unsigned long)time(NULL));
    while (true)
    {
        int data = rand() % 1024;
        rqp->PutData(data);
        std::cout << "Prodoct data done: " << data << std::endl;
        // sleep(1);
    }
}
int main()
{
    RingQueue rq;
    pthread_t c, p;
    pthread_create(&c, NULL, consumer, (void *)&rq);
    pthread_create(&p, NULL, producter, (void *)&rq);
    pthread_join(c, NULL);
    pthread_join(p, NULL);
}

更多代码实现参考

RingQueue · fortianyang/StudyForLinux - 码云 - 开源中国 (gitee.com)

相关推荐
筱源源9 分钟前
Kafka-linux环境部署
linux·kafka
小码农<^_^>25 分钟前
优选算法精品课--滑动窗口算法(一)
算法
羊小猪~~27 分钟前
神经网络基础--什么是正向传播??什么是方向传播??
人工智能·pytorch·python·深度学习·神经网络·算法·机器学习
成都古河云1 小时前
智慧场馆:安全、节能与智能化管理的未来
大数据·运维·人工智能·安全·智慧城市
算法与编程之美1 小时前
文件的写入与读取
linux·运维·服务器
软工菜鸡1 小时前
预训练语言模型BERT——PaddleNLP中的预训练模型
大数据·人工智能·深度学习·算法·语言模型·自然语言处理·bert
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
xianwu5431 小时前
反向代理模块
linux·开发语言·网络·git
Amelio_Ming1 小时前
Permissions 0755 for ‘/etc/ssh/ssh_host_rsa_key‘ are too open.问题解决
linux·运维·ssh
心灵彼岸-诗和远方1 小时前
Devops业务价值流:软件研发最佳实践
运维·产品经理·devops