经典进程同步问题

经典进程同步问题

进程同步

临界区

对共享资源进行访问的那段代码称为临界区,当有其他进程在这段代码中运行时,其他进程就不能在这段代码中执行。

为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。

// entry section
// critical section
// exit section

互斥与同步

  • 同步:多个进程的一定的先后执行关系。一组并发进程因直接制约而互相发送消息、进行互相合作、互相等待,使得各进程按一定的速度执行的过程。比如消费者需要等待仓库存在货物才可来消费,生产者在仓库没有摆满的情况下才能生产。
  • 互斥:多个进程在同一时刻只有一个进程能进入临界区。也就是说,一个进程正在访问临界资源,其他要访问该资源的进程必须等待。比如消费者和生产者不能同时访问仓库。

信号量

信号量(Semaphore)是一个整型变量,可以对其执行wait和post等操作。

  • wait:如果信号量大于 0 ,执行-1操作;如果信号量等于0,进程被阻塞,等待信号量大于0继续执行
  • post:对信号量执行+1操作
  • init:初始化信号量
  • destory:销毁信号量

wait和post操作需要被设计成原子操作,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。原子性保证了并发进程的隔离。

二元信号量即信号量的取值只能为0或者1,那么就成为了互斥量(Mutex),0表示临界区已经加锁,1表示临界区解锁。

经典模型

生产者消费者

使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。一个时间只能有一个人访问缓冲区。

为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty_sem记录空缓冲区的数量,full_sem记录满缓冲区的数量。其中,empty_sem信号量是在生产者进程中使用,当empty_sem不为 0 时,生产者才可以放入物品;full_sem信号量是在消费者进程中使用,当full_sem信号量不为0时,消费者才可以取走物品。

不论是生产者还是消费者,都不能先抢占互斥信号量再等待同步信号量,如果不这样做可能会发送这种情况:生产者首先进入了仓库,而此时仓库是满的,那么生产者就无法生产产品而等待消费者消费。消费者因为生产者占据着仓库,因此无法进入仓库消费,这样就导致导致生产者永远被阻塞,不会释放锁,消费者因此也会永远等待下去,发生死锁的情况。

/* 生产者消费者示例程序 */
#include <iostream>
#include <pthread.h>
#include <vector>
#include <semaphore.h>
#include <windows.h>

using namespace std;

// 仓库容量
#define MAX_PRODUCTS 32
// 生产者数量
#define PRODUCER 4
// 消费者数量
#define CONSUMER 2

// 线程类型
enum type
{
    prod,
    cons
};

// 传递给线程的参数
struct params
{
    int id;
    int type;
};

// 模拟仓库
vector<int> product_id;
// 互斥信号量
sem_t syn_sem;
// 同步信号
sem_t empty_sem;
sem_t full_sem;

// 初始化信号量
void init_sem()
{
    sem_init(&syn_sem, 0, 1);
    sem_init(&empty_sem, 0, MAX_PRODUCTS);
    sem_init(&full_sem, 0, 0);
}

// 销毁信号量
void destory_sem()
{
    sem_destroy(&syn_sem);
    sem_destroy(&empty_sem);
    sem_destroy(&full_sem);
}

// 生产者线程轨迹
void *producer(void *args)
{
    struct params *info = (struct params *)args;
    int pro_id = info->id;
    while (1)
    {
        Sleep(1000);
        // 等待仓库可取
        sem_wait(&empty_sem);
        // 等待仓库无人
        sem_wait(&syn_sem);
        int id = product_id.size();
        cout << "[prod] producer " << pro_id << " produce a product, id is " << id << endl;
        product_id.emplace_back(id);
        // 告知仓库可取
        sem_post(&full_sem);
        // 离开仓库
        sem_post(&syn_sem);
    }
}

// 消费者线程轨迹
void *consumer(void *args)
{
    struct params *info = (struct params *)args;
    int con_id = info->id;
    while (1)
    {
        Sleep(1000);
        // 等待仓库可取
        sem_wait(&full_sem);
        // 等待仓库无人
        sem_wait(&syn_sem);
        int id = product_id.back();
        product_id.pop_back();
        cout << "[consumer] consumer " << con_id << " consume a product, id is " << id << endl;
        // 告知仓库可放
        sem_post(&empty_sem);
        // 离开仓库
        sem_post(&syn_sem);
    }
}

int main()
{
    // 初始化线程
    init_sem();
    pthread_t threads[PRODUCER + CONSUMER];
    // 创建生成者线程
    for (int i = 0; i < PRODUCER; i++)
    {
        // 构造参数
        struct params *args = NULL;
        args = new struct params;
        args->id = i;
        args->type = prod;
        if (pthread_create(&threads[i], NULL, producer, (void *)args) != 0)
        {
            cout << "create a thread error" << endl;
        }
    }
    // 创建消费者线程
    for (int i = 0; i < CONSUMER; i++)
    {
        // 构造参数
        struct params *args = NULL;
        args = new struct params;
        args->id = i;
        args->type = cons;
        if (pthread_create(&threads[i], NULL, consumer, (void *)args) != 0)
        {
            cout << "create a thread error" << endl;
        }
    }
    // 等待线程完成主线程再结束
    pthread_exit(NULL);
    // 销毁信号量
    destory_sem();
}

运行结果如下

[prod] producer 3 produce a product, id is 0
[prod] producer 1 produce a product, id is 1
[prod] producer 0 produce a product, id is 2
[prod] producer 2 produce a product, id is 3
[consumer] consumer 1 consume a product, id is 3
[consumer] consumer 0 consume a product, id is 2
[prod] producer 0 produce a product, id is 2
[prod] producer 1 produce a product, id is 3
[consumer] consumer 0 consume a product, id is 3
[prod] producer 3 produce a product, id is 3
[prod] producer 2 produce a product, id is 4
[consumer] consumer 1 consume a product, id is 4
[prod] producer 2 produce a product, id is 4
[prod] producer 3 produce a product, id is 5
[prod] producer 1 produce a product, id is 6
[consumer] consumer 0 consume a product, id is 6
[consumer] consumer 1 consume a product, id is 5
[prod] producer 0 produce a product, id is 5
[prod] producer 2 produce a product, id is 6
[prod] producer 3 produce a product, id is 7
[prod] producer 1 produce a product, id is 8
[prod] producer 0 produce a product, id is 9
[consumer] consumer 1 consume a product, id is 9
[consumer] consumer 0 consume a product, id is 8
……
……
------本页内容已结束,喜欢请分享------

文章作者
能不能吃完饭再说
隐私政策
PrivacyPolicy
用户协议
UseGenerator
许可协议
NC-SA 4.0


© 版权声明
THE END
喜欢就支持一下吧
点赞24赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片