线程同步---信号量以及与互斥锁构建生产者消费者模型

本文详细介绍了信号量的概念,作为多线程同步的一种机制,信号量允许指定资源数量并控制线程的访问。在生产者消费者模型中,信号量用于协调生产者和消费者的资源分配。文章通过示例代码展示了如何使用C语言的`semaphore.h`库实现信号量,并避免了死锁问题。同时,强调了信号量相对于条件变量的优势在于无需程序员手动检查条件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

信号量(本文所指主要是多元信号量)
1.什么是信号量?
  • 信号量是用在多线程多任务同步的,通过信号量的资源数来决定是否阻塞线程
  • 信号量可以在没有资源时阻塞线程,也可以主动去增加某个线程所用到的资源数
  • 如果信号量的资源数充足,则可能发生多个线程同时访问共享资源,因此需要配合mutex一起使用,进行线程同步
2.主要的特点是什么?
  • 可以指定资源数目,比如在生产者消费者模型中的队列中存在的任务数空闲的任务坑位都是资源数目
  • 对比条件变量:条件变量需要程序员自己判断条件是否满足,在满足条件的情况下会阻塞相应的线程;但是信号量不需要
3.应用场景是什么?
  • 生产者消费者模型
  • 生产者线程对应一个信号量
  • 消费者线程对应一个信号量
4.常用的API函数
// 1.包含头文件
#include <semaphore.h>

// 2.定义信号量
sem_t sem;

// 初始化信号量
// sem: 信号量地址
// pshared: 0->线程同步   1->进程同步
// value: 资源数目
int sem_init(sem_t *sem, int pshared, unsigned int value);

// 访问资源
// 函数被调用,sem的资源数减1
// 当资源数 > 0, 线程不会阻塞,线程会占用一个资源,因此资源数-1;
// 当资源数 = 0, 表示资源被耗尽,线程阻塞,等待被唤醒
int sem_wait(sem_t *sem);

// 功能同上,但是如果资源数 = 0, 函数会立即返回错误码,线程不会阻塞
int sem_trywait(sem_t *sem);

// 功能同上,如果资源数 = 0, 线程阻塞,等待abs_timeout对应的时间后,解除阻塞,向下执行
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

// 调用该函数,会给相应的信号量的资源数加1
int sem_post(sem_t *sem);

// 查看信号量的资源数
int sem_getvalue(sem_t *sem, int *sval);
5.实现生产者消费者模型的代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

#define QUEUEMAX 10 

// mutex与semaphore
pthread_mutex_t mutex;
sem_t cons_sem;
sem_t prod_sem;

// 节点
typedef struct node
{
    int num;
    struct node* next;
}Node;

// 指向头结点的指针
Node* head = NULL;
int count = 0;

/* 节点插入方式如下所示
* head-->null
* newNode1
* head-->newNode1-->null
* newNode2
* head-->newNode2-->newNode1-->null
* .....
*/
void* producer(void* arg) {
    while (1) {
        sem_wait(&prod_sem);    // 生产资源减1,无生产资源时阻塞线程
        Node* newNode = (Node*)malloc(sizeof(Node));
        newNode->num = rand() % 100;

        pthread_mutex_lock(&mutex);
        newNode->next = head;
        head = newNode;
        count++;
        printf("+++Producer  id: %ld, number: %d, count: %d\n", pthread_self(), newNode->num, count);
        pthread_mutex_unlock(&mutex);
        sem_post(&cons_sem);    // 消费资源加1,消费者线程可感知

        sleep((unsigned int)rand() % 2);
    }
    return NULL;
}

void* consumer(void* arg) {
    while (1) {
        // 应该将sem_wait防置在mutex锁之前,这是为了防止可能存在的死锁
        /*
            假设mutex上锁放在sem_wait之前   
            pthread_mutex_lock(&mutex); 
            sem_wait(&cons_sem);
            如果某个消费者线程上锁之后,运行至sem_wait函数处,发现资源数变为0,因此当前线程阻塞;
            同时因为当前线程获取了锁,导致其他线程均被阻塞;
            此时所有的线程均被阻塞,造成死锁现象

            所以不能将sem_wait放置在mutex锁之后!!!!
        */
        sem_wait(&cons_sem);       
        pthread_mutex_lock(&mutex);
        Node* curNode = head;
        count--;
        printf("---Consumer  id: %ld, number: %d, count: %d\n", pthread_self(), curNode->num, count);
        head = head->next;
        free(curNode);
        pthread_mutex_unlock(&mutex);

        sem_post(&prod_sem);

        sleep((unsigned int)rand() % 2);
    }
    return NULL;
}

int main() {
    // 初始化锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    sem_init(&cons_sem, 0, 0);
    sem_init(&prod_sem, 0, QUEUEMAX);

    // t1是生产者线程数组 t2是消费者线程数组
    pthread_t t1[5], t2[5];

    // 创建线程
    for (int i = 0; i < 5; i++) {
        pthread_create(&t1[i], NULL, producer, NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_create(&t2[i], NULL, consumer, NULL);
    }

    // 回收线程
    for (int i = 0; i < 5; i++) {
        pthread_join(t1[i], NULL);
    }
    for (int i = 0; i < 5; i++) {
        pthread_join(t2[i], NULL);
    }

    // 销毁互斥锁
    pthread_mutex_destroy(&mutex);

    // 销毁semaphore
    sem_destroy(&cons_sem);
    sem_destroy(&prod_sem);

    return 0;
}
参考文献:

①《程序员的自我修养》
② https://subingwen.cn/linux/thread-sync/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

咖啡与乌龙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值