线程同步--条件变量,信号量

生产者和消费者模型

image-20240512142852414

案例
/*
    生产者消费者模型(粗略的版本)
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// 创建一个互斥量
pthread_mutex_t mutex;

struct Node{
    int num;
    struct Node *next;
};

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {

    // 不断的创建新的节点,添加到链表中,头插法
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;

        // 判断是否有数据
        if(head != NULL) {
            // 有数据
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // 没有数据
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);

    // 创建5个生产者线程,和5个消费者线程
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

条件变量

条件变量不是锁,可以引起线程阻塞,某个条件满足以后阻塞线程或解除线程,他不能解决数据混乱的问题

条件变量的类型 pthread_cond_t

这些函数是 POSIX 线程(pthread)库的一部分,用于在 C 和 C++ 程序中提供线程处理能力。这里讨论的特定函数与条件变量有关,条件变量是同步原语,允许线程挂起执行并等待某些条件发生。

条件变量相关函数
1. pthread_cond_init

此函数初始化条件变量。它接受两个参数:

  • cond:指向要初始化的条件变量的指针。
  • attr:指向 pthread_condattr_t 结构的指针,该结构指定条件变量的任何属性。如果使用默认属性,此参数可以为 NULL。
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
2. pthread_cond_destroy

此函数销毁条件变量,释放与之关联的任何资源。在不再需要已初始化的条件变量时应调用它,以避免内存泄漏。

  • cond:指向要销毁的条件变量的指针。
pthread_cond_destroy(&cond);
3. pthread_cond_wait

此函数会阻塞调用线程,直到指定条件被触发。调用时必须由调用线程锁定互斥锁;pthread_cond_wait 会自动释放互斥锁并使调用线程在条件变量上阻塞。

  • cond:指向要等待的条件变量的指针。
  • mutex:指向互斥锁的指针。
pthread_cond_wait(&cond, &mutex);
4. pthread_cond_timedwait

pthread_cond_wait 类似,但带有超时。调用线程被阻塞,直到指定条件被触发或超时发生。

  • cond:指向条件变量的指针。
  • mutex:指向互斥锁的指针。
  • abstime:指向 timespec 结构的指针,指定绝对超时时间。
struct timespec ts;
// 设置 ts 为某个绝对时间
pthread_cond_timedwait(&cond, &mutex, &ts);
5. pthread_cond_signal

此函数至少解除阻塞在指定条件变量上的一个线程。它不释放互斥锁,也不会将互斥锁从信号线程转移给被唤醒的线程。

  • cond:指向要发信号的条件变量的指针。
pthread_cond_signal(&cond);
6. pthread_cond_broadcast

此函数解除阻塞所有当前在指定条件变量上阻塞的线程。与 pthread_cond_signal 不同,后者唤醒至少一个等待线程,broadcast 唤醒所有等待线程。

  • cond:指向要广播的条件变量的指针。
pthread_cond_broadcast(&cond);

这些函数是多线程编程中管理共享资源和同步不同线程执行流的重要工具。有效使用它们可以帮助避免复杂的多线程应用中的竞态条件和死锁。

案例
/*
    条件变量的类型 pthread_cond_t
    int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
    int pthread_cond_destroy(pthread_cond_t *cond);
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
        - 等待,调用了该函数,线程会阻塞。
    int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
        - 等待多长时间,调用了这个函数,线程会阻塞,直到指定的时间结束。
    int pthread_cond_signal(pthread_cond_t *cond);
        - 唤醒一个或者多个等待的线程
    int pthread_cond_broadcast(pthread_cond_t *cond);
        - 唤醒所有的等待的线程
*/
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建条件变量
pthread_cond_t cond;

struct Node{
    int num;
    struct Node *next;
};

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {

    // 不断的创建新的节点,添加到链表中
    while(1) {
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        
        // 只要生产了一个,就通知消费者消费
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&mutex);
        usleep(100);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        // 判断是否有数据
        if(head != NULL) {
            // 有数据
            head = head->next;
            printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
            free(tmp);
            pthread_mutex_unlock(&mutex);
            usleep(100);
        } else {
            // 没有数据,需要等待
            // 当这个函数调用阻塞的时候,会对互斥锁进行解锁,当不阻塞的,继续向下执行,会重新加锁。
            pthread_cond_wait(&cond, &mutex);
            pthread_mutex_unlock(&mutex);
        }
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    // 创建5个生产者线程,和5个消费者线程
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    pthread_exit(NULL);

    return 0;
}

信号量

信号量的类型 sem_t

sem_init

sem_init(sem_t *sem, int pshared, unsigned int value): 初始化一个未命名的信号量。参数sem是指向信号量结构的指针,pshared指示信号量是在进程间共享(非0值)还是线程间共享(0值),value是信号量的初始值。

sem_destroy

sem_destroy(sem_t *sem): 销毁一个未命名的信号量,释放它可能占用的资源。参数sem是指向信号量结构的指针。

sem_wait

sem_wait(sem_t *sem): 等待信号量。如果信号量的值大于0,它将减少1并立即返回。如果信号量的值为0,调用线程将阻塞,直到信号量值变为非0。

sem_trywait

sem_trywait(sem_t *sem): 尝试等待信号量。它是sem_wait的非阻塞版本,如果信号量的值为0,则立即返回错误,而不是阻塞。

sem_timedwait

sem_timedwait(sem_t *sem, const struct timespec *abs_timeout): 在指定的超时时间内等待信号量。如果信号量在给定时间内变为可用,它将减少信号量的值并返回;如果超时,则返回错误。

sem_post

sem_post(sem_t *sem): 通过增加信号量的值来释放信号量,如果有线程因等待这个信号量而阻塞,它将被唤醒。

sem_getvalue

sem_getvalue(sem_t *sem, int *sval): 获取信号量的当前值。参数sval是一个指针,用于存储读取的信号量值。

案例

/*
    信号量的类型 sem_t
    int sem_init(sem_t *sem, int pshared, unsigned int value);
        - 初始化信号量
        - 参数:
            - sem : 信号量变量的地址
            - pshared : 0 用在线程间 ,非0 用在进程间
            - value : 信号量中的值

    int sem_destroy(sem_t *sem);
        - 释放资源

    int sem_wait(sem_t *sem);
        - 对信号量加锁,调用一次对信号量的值-1,如果值为0,就阻塞

    int sem_trywait(sem_t *sem);

    int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
    int sem_post(sem_t *sem);
        - 对信号量解锁,调用一次对信号量的值+1

    int sem_getvalue(sem_t *sem, int *sval);

    sem_t psem;
    sem_t csem;
    init(psem, 0, 8);
    init(csem, 0, 0);

    producer() {
        sem_wait(&psem);
        sem_post(&csem)
    }

    customer() {
        sem_wait(&csem);
        sem_post(&psem)
    }

*/

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

// 创建一个互斥量
pthread_mutex_t mutex;
// 创建两个信号量
sem_t psem;
sem_t csem;

struct Node{
    int num;
    struct Node *next;
};

// 头结点
struct Node * head = NULL;

void * producer(void * arg) {

    // 不断的创建新的节点,添加到链表中
    while(1) {
        sem_wait(&psem);
        pthread_mutex_lock(&mutex);
        struct Node * newNode = (struct Node *)malloc(sizeof(struct Node));
        newNode->next = head;
        head = newNode;
        newNode->num = rand() % 1000;
        printf("add node, num : %d, tid : %ld\n", newNode->num, pthread_self());
        pthread_mutex_unlock(&mutex);
        sem_post(&csem);
    }

    return NULL;
}

void * customer(void * arg) {

    while(1) {
        sem_wait(&csem);
        pthread_mutex_lock(&mutex);
        // 保存头结点的指针
        struct Node * tmp = head;
        head = head->next;
        printf("del node, num : %d, tid : %ld\n", tmp->num, pthread_self());
        free(tmp);
        pthread_mutex_unlock(&mutex);
        sem_post(&psem);
       
    }
    return  NULL;
}

int main() {

    pthread_mutex_init(&mutex, NULL);
    sem_init(&psem, 0, 8);
    sem_init(&csem, 0, 0);

    // 创建5个生产者线程,和5个消费者线程
    pthread_t ptids[5], ctids[5];

    for(int i = 0; i < 5; i++) {
        pthread_create(&ptids[i], NULL, producer, NULL);
        pthread_create(&ctids[i], NULL, customer, NULL);
    }

    for(int i = 0; i < 5; i++) {
        pthread_detach(ptids[i]);
        pthread_detach(ctids[i]);
    }

    while(1) {
        sleep(10);
    }

    pthread_mutex_destroy(&mutex);

    pthread_exit(NULL);

    return 0;
}

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值