[操作系统] 信号量与同步问题(semaphore & synchronization)

本文介绍了信号量作为并发进程同步工具的重要性,包括二值信号量和计数信号量的概念,以及它们在解决生产-消费者问题中的作用。通过C语言示例展示了如何使用信号量实现生产者和消费者之间的协作与竞争关系同步。
摘要由CSDN通过智能技术生成

信号量是一种比互斥锁更强大的同步工具,它可以提供更高级的方法来同步并发进程

定义

信号量S是一个整数变量,除了初始化之外,它只能被两个标准原子操作访问:P & V

原子操作(原语)
即指运行中不能被中断的操作
P -> wait() operation
V -> signal() operation

实现

P(S){
	while(S <= 0) 
		do nothing;
	S--;
}

V(S){
	S++;
}

Binary Semaphore

顾名思义,二值信号量的值只能是0或1,通常将其初始化为1,用于实现互斥锁的功能

semaphore mutex = 1;
process p
{
	P(mutex);  // 相当于acquire lock
	critical section;  // 临界区
	V(metex);  // 相当于release lock
}

mutex lock

如图所示,两辆车争夺一条匝道
假设此时,蓝车进入匝道, 蓝车执行P(mutex)操作

  1. mutex初始值为1,执行P(mutex)操作, 由于mutex > 0 不进入循环,执行mutex--mutex值为0
  2. 此时红车如果想要进入匝道,执行P(mutex)操作,由于此时mutex满足循环条件,它将在此忙式等待(Busy Waiting, 指占用CPU执行空循环实现等待)
  3. 等到蓝车驶出匝道,执行V(mutex)操作,mutex++mutex值重新为1
  4. 之后红车P操作 循环条件不满足,执行mutex--,红车驶入匝道,占用匝道资源。假如之后还有车辆驶来,那么它们进入Busy Waiting,以此往复

Counting Semaphore

一般信号量的取值可以是任意数值,用于控制并发进程对共享资源的访问

semaphore

有了前边的分析,我们可以轻松理解此图。
图中有多辆车共享两条匝道,即信号量road初始值为2
简要分析流程(假设按图中顺序)

  1. 红车P操作后,road = 1,驶入上边的匝道
  2. 蓝车1执行P操作,由于不满足循环条件,road = 0 ,驶入下边的匝道
  3. 蓝车2执行P操作,满足循环条件,进入Busy Waiting状态
    随后只要红车或者蓝车1 执行了V操作,那么road值将自增,相当于驶出匝道,蓝车2即可进入匝道

如果此时红车和蓝车1都驶出了匝道,只有蓝车2在匝道,此时road即为1

  • P、V操作是成对出现的
  • 信号量代表可用共享资源总量

信号量实现同步

生产-消费者问题

  • 缓冲区:相当于存放产品的数组
  • 生产者:负责生产产品,当产品数超过最大值时停止生产
  • 消费者:负责消费产品,当产品数小于1时停止消费

分析其中的关系可知,

  • 生产者(线程)与消费者(线程)之间是协作关系
  • 同类线程之间为竞争关系
    其中竞争关系,可以通过互斥锁来实现

如何实现协作关系?

consumer&producer problem

如图所示,我们可以设置两个信号量 emptyfull ,其中empty负责控制资源总量(K)
分析图中伪代码流程:
假设有三个生产者线程,两个消费者线程

  1. consumer-01 线程执行 P(full)操作,由于不满足循环条件,进入busy waiting状态
  2. consumer-02 线程同样进入busy waiting状态,对应实际场景,缓冲区还没有商品,消费者无法消费。
  3. producer-03 线程执行P(empty)操作,empty = k-1,开始生产产品后,执行V(full)操作, full = 1
  4. 假设随后CPU依次调度producer-01 和 producer-02线程,此时full = 3, empty = k-3。可供两个消费者线程争夺资源,假设K = 3, 那么此时empty = 0, 相当于缓存区已放满商品,此时如果新来一个生产者 producer-04线程,它将进入等待。

注: 同类线程之间为竞争关系,相当于图中临界区前后(empty 和 full 之间),需要再加入P(mutex) 和 V(mutex)操作实现互斥

参考代码(C)

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

#define THREAD_NUM 4  // 线程总数
#define K 5  // 资源总数
typedef int item;

item Buffer[K];
sem_t empty, full, mutex;

int cur = 0;
int in = 0, out = 0;

void *producer(void *arg)
{
    while (1)
    {
        sem_wait(&empty);  // p(empty)
        sem_wait(&mutex); // p(mutex)
        printf("%lu, 生产了一件商品, current = %d\n", pthread_self(), ++cur);
        Buffer[in] = cur; 
        in = (in + 1) % K;

        sem_post(&mutex); // v(mutex)
        sem_post(&full); // v(full)
    }
    pthread_exit(0);
}
void *consumer(void *arg)
{
    while (1)
    {
        sem_wait(&full);
        sem_wait(&mutex);

        Buffer[out] = -1;
        out = (out + 1) % K;
        printf("%lu, 取走了一件商品, current = %d\n", pthread_self(), --cur);

        sem_post(&mutex);
        sem_post(&empty);
    }

    pthread_exit(0);
}
int main(int argc, char const *argv[])
{
    pthread_t ticketAgent_tid[THREAD_NUM];
    // 初始化操作
    sem_init(&empty, 0, K);
    sem_init(&full, 0, 0);
    sem_init(&mutex, 0, 1);
	
	// 创建线程
    pthread_create(ticketAgent_tid, NULL, producer, NULL);
    pthread_create(ticketAgent_tid + 1, NULL, consumer, NULL);
    pthread_create(ticketAgent_tid + 2, NULL, consumer, NULL);
    pthread_create(ticketAgent_tid + 3, NULL, producer, NULL);

    pthread_join(ticketAgent_tid[0], NULL);
    pthread_join(ticketAgent_tid[1], NULL);
    pthread_join(ticketAgent_tid[2], NULL);
    pthread_join(ticketAgent_tid[3], NULL);
	
	// 销毁信号量
    sem_destroy(&mutex);
    sem_destroy(&full);
    sem_destroy(&empty);
    return 0;
}
  • 25
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值