【Linux】pthread多线程同步

article/2025/6/7 23:39:23

参考文章:https://blog.csdn.net/Alkaid2000/article/details/128121066

一、线程同步

  • 线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便携的共享是有代价的;必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。(否则会出现数据安全现象)

  • 临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应中断该片段的执行。

  • 线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到线程完成操作,其他线程才能对内存地址进行操作,而其他线程则处于等待状态,肯定会影响效率,但是起码他只是影响了代码的一小部分。

二、互斥锁

  • 为避免线程更新共享变量时出现问题,可以使用互斥量(mutex——mutual exclusion)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。

  • 互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或报错失败,具体取决于加锁时使用的方法。

  • 一旦线程锁定互斥量,随即称为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议:

    1. 针对共享资源锁定互斥量。
    2. 访问共享资源。
    3. 对互斥量解锁。
  • 如果多个线程试图执行这一块代码(一个临界区),事实上只有一个线程能够持有该互斥量(其他线程将遭到阻塞),即同时只有一个线程能够进入这段代码区域。

在这里插入图片描述

  • 关于互斥量有如下的操作函数,与线程属性相似,它具有一个互斥量类型pthread_mutex_t
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);int pthread_mutex_destroy(pthread_mutex_t *mutex);int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 下面的代码模拟多线程环境下的卖票场景,通过互斥锁保证多个线程安全访问共享资源(剩余票数),避免竞态条件。
size_t tickets = 100;
pthread_mutex_t mutex;void *sellTicket(void *arg)
{while (1){pthread_mutex_lock(&mutex);if (tickets > 0){std::cout << "the rest of tickets are " << --tickets << " thread ID:" << pthread_self() << std::endl;usleep(1000);}else{pthread_mutex_unlock(&mutex);break;}pthread_mutex_unlock(&mutex);}return NULL;
}void test1()
{pthread_t t1, t2, t3;pthread_mutex_init(&mutex, NULL);pthread_create(&t1, NULL, sellTicket, NULL);pthread_create(&t2, NULL, sellTicket, NULL);pthread_create(&t3, NULL, sellTicket, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_mutex_destroy(&mutex);
}
2.2 核心代码分析
size_t tickets = 100;
pthread_mutex_t mutex;void *sellTicket(void *arg) {while (1) {pthread_mutex_lock(&mutex); // 加锁,进入临界区if (tickets > 0) {std::cout << "剩余票数: " << --tickets << " 线程ID: " << pthread_self() << std::endl;usleep(1000); // 模拟售票耗时} else {pthread_mutex_unlock(&mutex); // 解锁,退出临界区break;}pthread_mutex_unlock(&mutex); // 解锁,退出临界区}return NULL;
}
2.3 关键API详解:互斥锁(pthread_mutex_t
2.3.1 初始化函数
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr
);
  • 参数
    • mutex:互斥锁变量指针,用于存储锁的状态。
    • attr:互斥锁属性,NULL表示使用默认属性。
      • 常见属性:
        • PTHREAD_MUTEX_NORMAL:普通锁,不检测死锁。
        • PTHREAD_MUTEX_ERRORCHECK:错误检查锁,检测死锁并返回错误。
        • PTHREAD_MUTEX_RECURSIVE:递归锁,允许同一线程多次加锁。
  • 返回值
    • 成功:返回0。
    • 失败:返回错误码(如EAGAIN资源不足,ENOMEM内存分配失败)。
2.3.2 加锁函数
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime
);
  • 参数
    • mutex:互斥锁指针。
    • abstime:超时时间(绝对时间,如CLOCK_REALTIME)。
  • 返回值
    • pthread_mutex_lock:成功返回0,失败返回错误码(如EDEADLK检测到死锁)。
    • pthread_mutex_trylock:锁可用时返回0,不可用时返回EBUSY(不阻塞)。
    • pthread_mutex_timedlock:超时返回ETIMEDOUT,其他同lock
2.3.3 解锁函数
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • 参数
    • mutex:互斥锁指针。
  • 返回值
    • 成功:返回0。
    • 失败:返回错误码(如EPERM当前线程未持有锁)。
2.3.4 销毁函数
int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • 注意
    • 必须在锁未被持有时调用。
    • 销毁后不可再使用,需重新初始化。
2.3.5 运行结果

在这里插入图片描述

死锁
  • 在使用互斥量的时候,会可能出现一些问题,这些问题就是叫死锁。

  • 有时候,一个线程需要同时访问两个或更多不同的共享资源,就好比我们有两个共享资源AB,如果我们只加一个互斥量,同时锁住AB,如果AB离得近还好说,但是如果它中间隔着有一大段代码,那还是再考虑加个锁吧。所以就有了每个资源又都由不同的互斥量管理,当超过一个线程加锁同一组互斥量时,就有可能发生死锁。

  • 两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,他们都无法推进下去,此时称系统处于死锁状态或系统产生了死锁。

死锁的几种场景:

  • 忘记释放锁
  • 重复加锁(一个线程加了两道锁,两个锁都是一样的锁)
  • 多线程多锁,抢占锁资源

在这里插入图片描述

下面是死锁的一个案例代码

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>// 创建2个互斥量
pthread_mutex_t mutex1, mutex2;void * workA(void * arg) {pthread_mutex_lock(&mutex1);sleep(1);pthread_mutex_lock(&mutex2);printf("workA....\n");// 解锁顺序需要相反pthread_mutex_unlock(&mutex2);pthread_mutex_unlock(&mutex1);return NULL;
}void * workB(void * arg) {//注意这里加锁的顺序与A是相反的,所以它首先拿到了另外一个锁pthread_mutex_lock(&mutex2);sleep(1);pthread_mutex_lock(&mutex1);printf("workB....\n");pthread_mutex_unlock(&mutex1);pthread_mutex_unlock(&mutex2);return NULL;
}int main() {// 初始化互斥量pthread_mutex_init(&mutex1, NULL);pthread_mutex_init(&mutex2, NULL);// 创建2个子线程pthread_t tid1, tid2;pthread_create(&tid1, NULL, workA, NULL);pthread_create(&tid2, NULL, workB, NULL);// 回收子线程资源pthread_join(tid1, NULL);pthread_join(tid2, NULL);// 释放互斥量资源pthread_mutex_destroy(&mutex1);pthread_mutex_destroy(&mutex2);return 0;
}
如何避免死锁

当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁,那么死锁只有同时满足以下四个条件才会发生:

  1. 互斥条件。
  2. 持有并等待条件。
  3. 不可剥夺条件。
  4. 环路等待条件。

互斥条件:对于互斥条件是指多个线程不能同时同一个资源:如果线程 A 已经持有的资源,不能再同时被线程 B 持有,如果线程 B 请求获取线程 A 已经占用的资源,那线程 B 只能等待,直到线程 A 释放了资源。

在这里插入图片描述

持有并等待条件:持有并等待条件是指,当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 C 持有了,所以线程 A 就会处于等待状态,但是线程 A 在等待资源 2 的同时并不会释放自己已经持有的资源 1

在这里插入图片描述

不可剥夺条件:不可剥夺条件是指,当线程已经持有了资源 ,在自己使用完之前不能被其他线程获取,线程 B 如果也想使用此资源,则只能在线程 A 使用完并释放后才能获取。
在这里插入图片描述

环路等待条件:环路等待条件指的是,在死锁发生的时候,两个线程获取资源的顺序构成了环形链。比如,线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图。也就是上述那个代码的情况。

在这里插入图片描述

  • 那么避免死锁的问题就需要破坏其中一个条件即可,那么最常见的方法且可行的就是使用资源有序分配法,来破坏环路等待条件。

  • 线程 A 和 线程 B 获取资源的顺序要一样,当线程 A 是先尝试获取资源 A,然后尝试获取资源 B 的时候,线程 B 同样也是先尝试获取资源 A,然后尝试获取资源 B。也就是说,线程 A 和 线程 B 总是以相同的顺序申请自己想要的资源。这样就可以打破死锁了:

在这里插入图片描述

三、读写锁

  • 当有一个线程已经持有互斥锁时,互斥锁将所有试图进入临界区的线程都阻塞住。但是考虑一种情形,当前持有互斥锁的线程只是要读访问共享资源,而同时有其他几个线程也想读取这个共享资源,但是由于互斥锁的排他性,所有其他线程都无法获取锁,也就无法读访问共享资源了,但是实际上多个线程同时读访问共享资源并不会导致问题。

  • 在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个读出,但只允许一个写入的需求,线程提供了读写锁来实现。

读写锁的特点:

  • 如果有其他线程读数据,则允许其他线程执行读操作,但不允许写操作。
  • 如果有其他线程写数据,则其他线程都不允许读、写操作。
  • 写是独占的,写的优先级高。

那么关于读写锁相关的操作函数,有如下,当然,这里也有一个结构体,表达了读写锁的属性pthread_rwlock_t,这里也是一把锁,只不过是可以设置为读或者是写:

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
3.1 功能描述

使用读写锁实现“多读单写”场景:多个读线程可并发访问资源,写线程独占资源,提升读多写少场景的性能。


pthread_rwlock_t rwLock;size_t resource = 0;
void *readTask(void *arg)
{for (int i = 0; i < 100; ++i){pthread_rwlock_rdlock(&rwLock);std::cout << "----------Read Task----------" << std::endl;std::cout << "resouce = " << resource << " thread ID = " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock);usleep(2000);}return NULL;
}void *writeTask(void *arg)
{for (int i = 0; i < 100; ++i){pthread_rwlock_wrlock(&rwLock);resource++;std::cout << "----------Write Task----------" << std::endl;std::cout << "resouce = " << resource << " thread ID = " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock);usleep(1000);}return NULL;
}void test2()
{pthread_t t1, t2, t3;pthread_rwlock_init(&rwLock, NULL);pthread_create(&t1, NULL, readTask, NULL);pthread_create(&t2, NULL, readTask, NULL);pthread_create(&t3, NULL, writeTask, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_rwlock_destroy(&rwLock);
}
3.2 核心代码分析
pthread_rwlock_t rwLock;
size_t resource = 0;void *readTask(void *arg) {for (int i = 0; i < 100; ++i) {pthread_rwlock_rdlock(&rwLock); // 加读锁std::cout << "读取资源: " << resource << " 线程ID: " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock); // 解锁usleep(2000);}return NULL;
}void *writeTask(void *arg) {for (int i = 0; i < 100; ++i) {pthread_rwlock_wrlock(&rwLock); // 加写锁resource++;std::cout << "写入资源: " << resource << " 线程ID: " << pthread_self() << std::endl;pthread_rwlock_unlock(&rwLock); // 解锁usleep(1000);}return NULL;
}
3.3 关键API详解:读写锁(pthread_rwlock_t
3.3.1 初始化与销毁
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr
);int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
  • 参数
    • attr:读写锁属性,NULL表示默认属性。
      • 常见属性:
        • PTHREAD_RWLOCK_PREFER_READER_NP:默认,读者优先(可能导致写者饥饿)。
        • PTHREAD_RWLOCK_PREFER_WRITER_NP:写者优先(高并发下可能降低性能)。
3.3.2 加读锁函数
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict abstime
);
  • 返回值
    • pthread_rwlock_rdlock:成功返回0,失败返回错误码。
    • pthread_rwlock_tryrdlock:锁不可用时返回EBUSY(不阻塞)。
3.3.3 加写锁函数
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,const struct timespec *restrict abstime
);
  • 返回值
    • 同读锁函数,但写锁需等待所有读锁释放。
3.3.4 解锁函数
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
  • 注意
    • 读锁和写锁使用相同的解锁函数。
    • 必须由持有锁的线程调用。
3.3.5 运行结果

在这里插入图片描述

四、生产者-消费者模型

一、生产者-消费者模型概念

假设有两个进程(或线程)A、B和一个固定大小的缓冲区,A进程产生的数据放入缓冲区,B进程从缓冲区中取出数据进行计算,这就是一个简单的生产者和消费者模型。这里的A相当于生产者,B相当于消费者:

在这里插入图片描述

这个模型所需要的对象有:

  • 生产者
  • 消费者
  • 容器

为什么要用这个模型

  • 在多线程开发中,如果生产者生产数据的速度很快,而消费者消费数据的速度很慢,那么生产者就必须等待消费者消费完数据才能够继续生产数据,因为生产过多的数据可能会导致存储不足;同理如果消费者的速度大于生产者那么消费者就会经常处理等待状态,所以为了达到生产者和消费者生产数据和消费数据之间的平衡,那么就需要一个缓冲区用来存储生产者生产的数据,所以就引入了这个模式。

  • 简单来说,这里缓冲区的作用就是为了平衡生产者和消费者的数据处理能力,一方面起到缓存作用,另一方面达到解耦合作用。那么如何去解决这个问题,需要使用条件变量和信号量来解决这个问题

生产者-消费者模型特点

  • 保证生产者不会在缓冲区满的时候继续向缓冲区中放入数据,而消费者也不会在缓冲区空的时候消耗数据。

  • 当缓冲区满时,生产者会进入休眠状态,当下次消费者开始消耗缓冲区的数据时,生产者才会被唤醒,开始往缓冲区中添加数据;当缓冲区空时,消费者也会进入休眠状态,直到生产者往缓冲区中添加数据时才会被唤醒。

五、条件变量

  • 条件变量不是锁,但是它可以引起线程阻塞,满足条件后解除阻塞,它是配合互斥量来实现的,当然它也有一个结构体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);
5.1 功能描述

通过条件变量(pthread_cond_t)实现生产者-消费者同步:生产者生成数据后通知消费者,消费者无数据时阻塞等待。


typedef struct Node
{int value;struct Node *next;
} Node;pthread_cond_t cond;
struct Node *head = NULL;
void *produceTask(void *arg)
{for (int i = 0; i < 100; ++i){pthread_mutex_lock(&mutex);struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));newNode->value = i;newNode->next = head;head = newNode;std::cout << "----------Produce Task----------" << std::endl;std::cout << "value = " << newNode->value << " thread ID = " << pthread_self() << std::endl;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond); // 通知消费线程usleep(1000);}return NULL;
}void *consumeTask(void *arg)
{for (int i = 0; i < 50; ++i){pthread_mutex_lock(&mutex);Node *node = head;if (node != NULL){std::cout << "----------Consume Task----------" << std::endl;std::cout << "value = " << node->value << " thread ID = " << pthread_self() << std::endl;head = head->next;free(node);node = NULL;}else{pthread_cond_wait(&cond, &mutex); // 阻塞等待,让出锁,直到收到信号,重新加锁}pthread_mutex_unlock(&mutex);}return NULL;
}void test3()
{pthread_cond_init(&cond, NULL);pthread_mutex_init(&mutex, NULL);pthread_t t1, t2, t3;pthread_create(&t1, NULL, produceTask, NULL);pthread_create(&t2, NULL, consumeTask, NULL);pthread_create(&t3, NULL, consumeTask, NULL);pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);
}
5.2 核心代码分析
pthread_cond_t cond;
struct Node *head = NULL;void *produceTask(void *arg) {for (int i = 0; i < 100; ++i) {pthread_mutex_lock(&mutex);// 生产数据(简化为链表头插入)Node *newNode = malloc(sizeof(Node));newNode->value = i;newNode->next = head;head = newNode;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond); // 通知消费者有新数据usleep(1000);}return NULL;
}void *consumeTask(void *arg) {for (int i = 0; i < 50; ++i) {pthread_mutex_lock(&mutex);// 无数据时等待条件变量while (head == NULL) {pthread_cond_wait(&cond, &mutex); // 释放锁并阻塞,唤醒时重新加锁}// 消费数据Node *node = head;head = head->next;free(node);pthread_mutex_unlock(&mutex);}return NULL;
}
5.3 关键API详解:条件变量(pthread_cond_t
5.3.1 初始化与销毁
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr
);int pthread_cond_destroy(pthread_cond_t *cond);
  • 参数
    • attr:条件变量属性,NULL表示默认属性。
      • 常见属性:
        • PTHREAD_PROCESS_SHARED:进程间共享(需配合共享内存使用)。
5.3.2 等待函数
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
);
  • 参数
    • mutex:关联的互斥锁,用于保护条件判断。
    • abstime:超时时间(绝对时间)。
  • 执行流程
    1. 原子性地释放mutex并阻塞当前线程。
    2. 被唤醒时,重新获取mutex并返回。
  • 返回值
    • pthread_cond_wait:成功返回0。
    • pthread_cond_timedwait:超时返回ETIMEDOUT
5.3.3 通知函数
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
  • 区别
    • pthread_cond_signal:唤醒至少一个等待线程(由调度器决定)。
    • pthread_cond_broadcast:唤醒所有等待线程(适用于多个消费者场景)。
  • 注意
    • 通常在解锁后调用,但在锁内调用更安全(避免唤醒丢失)。
5.4.4 运行结果

在这里插入图片描述

六、信号量

  • 信号量这个东西也是用于阻塞线程的,相当于是在亮灯,他只能告诉线程当前数据是否可读可写,但是它并不能真正的保证数据的安全问题,所以也需要配合互斥锁的使用,当然也有一个结构体sem_t,其操作的代码有如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);int sem_init(sem_t *sem, int pshared, unsigned int value);int sem_wait(sem_t *sem);int sem_trywait(sem_t *sem);int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);int sem_post(sem_t *sem);int sem_getvalue(sem_t *sem, int *sval);
6.1 功能描述

使用计数信号量(sem_t)控制生产者和消费者的并发数量:

  • pSem:控制生产者可使用的空闲槽位数量(初始值5)。
  • cSem:控制消费者可使用的数据项数量(初始值0)。

sem_t pSem, cSem;void *produceTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&pSem); // producer信号量-1pthread_mutex_lock(&mutex);struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));newNode->value = i;newNode->next = head;head = newNode;int semCount = 0;int ret = sem_getvalue(&pSem,&semCount);std::cout << "----------Produce Task----------" << std::endl;std::cout << "value = " << newNode->value << " thread ID = " << pthread_self() << "semCount = " << semCount  <<std::endl;pthread_mutex_unlock(&mutex);sem_post(&cSem); // consumer信号量+1}return NULL;
}void *consumeTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&cSem); // consumer 信号量-1pthread_mutex_lock(&mutex);Node *node = head;int semCount = 0;int ret = sem_getvalue(&cSem,&semCount);std::cout << "----------Consume Task----------" << std::endl;std::cout << "value = " << node->value << " thread ID = " << pthread_self() << "semCount = " << semCount << std::endl;head = head->next;free(node);node = NULL;pthread_mutex_unlock(&mutex);sem_post(&pSem); // producer 信号量+1}return NULL;
}
void test4()
{pthread_mutex_init(&mutex, NULL);sem_init(&cSem, 0, 0);sem_init(&pSem, 0, 5);pthread_t pThread[5], cThread[5];for (int i = 0; i < 5; ++i){pthread_create(&pThread[i], NULL, produceTaskSem, NULL);pthread_create(&cThread[i], NULL, consumeTaskSem, NULL);}for (int i = 0; i < 5; ++i){pthread_join(pThread[i], NULL);pthread_join(cThread[i], NULL);}pthread_mutex_destroy(&mutex);sem_destroy(&pSem);sem_destroy(&cSem);
}
6.2 核心代码分析

sem_t pSem, cSem;void *produceTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&pSem); // producer信号量-1pthread_mutex_lock(&mutex);struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));newNode->value = i;newNode->next = head;head = newNode;int semCount = 0;int ret = sem_getvalue(&pSem,&semCount);std::cout << "----------Produce Task----------" << std::endl;std::cout << "value = " << newNode->value << " thread ID = " << pthread_self() << "semCount = " << semCount  <<std::endl;pthread_mutex_unlock(&mutex);sem_post(&cSem); // consumer信号量+1}return NULL;
}void *consumeTaskSem(void *arg)
{for (int i = 0; i < 100; ++i){sem_wait(&cSem); // consumer 信号量-1pthread_mutex_lock(&mutex);Node *node = head;int semCount = 0;int ret = sem_getvalue(&cSem,&semCount);std::cout << "----------Consume Task----------" << std::endl;std::cout << "value = " << node->value << " thread ID = " << pthread_self() << "semCount = " << semCount << std::endl;head = head->next;free(node);node = NULL;pthread_mutex_unlock(&mutex);sem_post(&pSem); // producer 信号量+1}return NULL;
}
6.3 关键API详解:信号量(sem_t
6.3.1 初始化与销毁
int sem_init(sem_t *sem,int pshared,unsigned int value
);int sem_destroy(sem_t *sem);
  • 参数
    • pshared
      • 0:信号量在线程间共享(保存在进程内存中)。
      • 非0:信号量在进程间共享(需配合共享内存使用)。
    • value:信号量初始值(如5表示5个空闲槽位)。
  • 注意
    • 销毁前需确保没有线程在等待该信号量。
6.3.2 P操作(等待)
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *restrict sem,const struct timespec *restrict abstime
);
  • 执行逻辑
    • sem_wait:信号量减1,若结果为负则阻塞。
    • sem_trywait:非阻塞版本,信号量不足时返回EAGAIN
    • sem_timedwait:超时返回ETIMEDOUT
6.3.3 V操作(释放)
int sem_post(sem_t *sem);
  • 执行逻辑
    • 信号量加1。
    • 若有线程因等待该信号量而阻塞,则唤醒其中一个。
6.3.4 获取当前值(调试用)
int sem_getvalue(sem_t *restrict sem,int *restrict sval
);
  • 返回值
    • 成功:返回0,*sval存储当前信号量值。
    • 失败:返回错误码。
  • 注意
    • 返回值可能在调用后立即被其他线程修改,仅供调试参考。
6.3.5 运行结果

在这里插入图片描述

七、多线程同步机制对比

机制核心功能关键API适用场景
互斥锁保证互斥访问lock, unlock所有互斥场景
读写锁多读单写优化rdlock, wrlock, unlock读多写少场景
条件变量线程间条件同步wait, signal, broadcast生产者-消费者、任务通知等
信号量控制有限资源的并发访问wait, post连接池、线程池等资源管理

更多资料:https://github.com/0voice


http://www.hkcw.cn/article/avDqPRSswf.shtml

相关文章

Spring AOP:面向切面编程 详解代理模式

文章目录 AOP介绍什么是Spring AOP&#xff1f;快速入门SpringAop引入依赖Aop的优点 Spring Aop 的核心概念切点(Pointcut)连接点、通知切面通知类型PointCut注解切面优先级Order切点表达式executionwithinthistargetargsannotation自定义注解 Spring AOP原理代理模式&#xff…

Ubuntu20.04用root(管理员身份)启动vscode

1.终端输入 code --user-data-dir~/.vscode --no-sandbox .

第7章 :面向对象

一、面向对象 面向过程&#xff1a;关心的是我该怎么做&#xff0c;一步步去实现这个功能 面向对象&#xff1a;关心的是我该让谁去做&#xff0c;去调用对象的操作来实现这个功能 面向对象 对象&#xff08;Object&#xff09; 分类&#xff08;Classification&#xff09; …

嵌入式linux八股文

1.指针的大小 指针大小的计算方式。指针的大小并非由其类型决定&#xff0c;而是与编译器的位数相关。具体来说&#xff0c;在 32 位的系统下&#xff0c;指针的大小为 4 个字节&#xff0c;而在 64 位的系统下&#xff0c;指针的大小为 8 个字节。通过示例代码的运行&#xf…

python可视化:端午假期旅游火爆原因分析

python可视化&#xff1a;端午假期旅游火爆原因分析 2025年的旅游市场表现强劲&#xff1a; 2025年端午假期全社会跨区域人员流动量累计6.57亿人次&#xff0c;日均2.19亿人次&#xff0c;同比增长3.0%。入境游订单同比大涨近90%&#xff0c;门票交易额&#xff08;GMV&#…

ubuntu22.04安装taskfile

sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -dsudo mv ./bin/task /usr/local/bin/测试 task --version

Ubuntu22.04 安装 Miniconda3

Conda 是一个开源的包管理系统和环境管理系统&#xff0c;可用于 Python 环境管理。 Miniconda 是一个轻量级的 Conda 发行版。Miniconda 包含了 Conda、Python和一些基本包&#xff0c;是 Anaconda 的精简版本。 1.下载安装脚本 在 conda官网 找到需要的安装版本&#xff0…

LRC and VIP

//首先排除所有数相等的情况,再把最大值放在一个组&#xff0c;那么最大值的gcd就等于其本身&#xff0c;再判断剩下的gcd是否等于最大值就可以了 #include<bits/stdc.h> using namespace std;const int N1e3100; int a[N]; map<int,int>mapp; int main(){int t;ci…

AI Agent开发第78课-大模型结合Flink构建政务类长公文、长文件、OA应用Agent

开篇 AI Agent2025确定是进入了爆发期,到处都在冒出各种各样的实用AI Agent。很多人、组织都投身于开发AI Agent。 但是从3月份开始业界开始出现了一种这样的声音: AI开发入门并不难,一旦开发完后没法用! 经历过至少一个AI Agent从开发到上线的小伙伴们其实都听到过这种…

统信 UOS 服务器版离线部署 DeepSeek 攻略

日前&#xff0c;DeepSeek 系列模型因拥有“更低的成本、更强的性能、更好的体验”三大核心优势&#xff0c;在全球范围内备受瞩目。 本次&#xff0c;我们为大家提供了在统信 UOS 服务器版 V20&#xff08;AMD64 或 ARM64 架构&#xff09;上本地离线部署 DeepSeek-R1 模型的…

6月2日day43打卡

复习日 作业&#xff1a; kaggle找到一个图像数据集&#xff0c;用cnn网络进行训练并且用grad-cam做可视化 进阶&#xff1a;并拆分成多个文件 任务写了两天&#xff0c;第一天找到一个数据集Stanford Cars Dataset&#xff08;斯坦福汽车数据集&#xff09;&#xff1a; 1. 基…

机器学习——聚类算法

一、聚类的概念 根据样本之间的相似性&#xff0c;将样本划分到不同的类别中的一种无监督学习算法。 细节&#xff1a;根据样本之间的相似性&#xff0c;将样本划分到不同的类别中&#xff1b;不同的相似度计算方法&#xff0c;会得到不同的聚类结果&#xff0c;常用的相似度…

蓝桥杯_DS18B20温度传感器---新手入门级别超级详细解析

目录 一、引言 DS18B20的原理图 单总线简介&#xff1a; ​编辑暂存器简介&#xff1a; DS18B20的温度转换与读取流程 二、代码配置 maic文件 疑问 关于不同格式化输出符号的使用 为什么要rd_temperature()/16.0&#xff1f; onewire.h文件 这个配置为什么要先读lo…

SuperMap GIS基础产品FAQ集锦(20250603)

一、SuperMap iDesktopX 问题1&#xff1a;这种投影坐标如何转换成China_2000的&#xff1f; 11.2.0 【解决办法】在数据源属性中&#xff0c;选择坐标系下的投影转换&#xff0c;然后指定转换结果的坐标系为China_2000 问题2&#xff1a;SuperMap iDesktopX 影像导出时&am…

【js 图片批量自定义打包下载】

压缩图片打包本地下载 一、依赖安转二、函数封装三、打包压缩四、应用五、示例图 一、依赖安转 打包工具 npm install file-saver --save npm install jszip --save二、函数封装 对图片进行处理 function getBase64Image(src) {return new Promise((resolve, reject) > …

如何轻松地将数据从 iPhone传输到iPhone 16

对升级到 iPhone 16 感到兴奋吗&#xff1f;恭喜&#xff01;然而&#xff0c;除了兴奋之外&#xff0c;学习如何将数据从 iPhone 传输到 iPhone 16 也很重要。毕竟&#xff0c;那些重要的联系人、笔记等都是不可或缺的。为了实现轻松的iPhone 到 iPhone 传输&#xff0c;我们总…

Adobe Acrobat——设置PDF打印页面的大小

1. 打开 PDF 文件&#xff1b; 2. 点击菜单栏的 “文件” → “打印”&#xff1b; 3. 在打印对话框中&#xff0c;点击 “属性”&#xff1b; 4. 点击 “布局”→ “高级”&#xff1b; 5. 点击 “纸张规格”&#xff0c;选择 “PostScript 自定义页面大小”&#xff0c;然后…

胜牌™全球成为2026年FIFA世界杯™官方赞助商

胜牌全球将首次与国际足联&#xff08;FIFA&#xff09;旗舰赛事建立合作关系。 此次赞助恰逢美国首个润滑油品牌即将迎来160周年之际&#xff0c;其国际扩张步伐正在加快。 在这项全球顶级赛事筹备期间&#xff0c;胜牌全球将通过各种富有创意的零售和体验活动与球迷互动。 …

mpg123在MSVC编译器中使用。

官网下载&#xff1a; 下载后打开以下窗口程序&#xff1a; 在此窗口程序中打开所下载的mpg123文件夹。在此文件夹中输入以下命令&#xff1a; dumpbin /EXPORTS libsyn123-0.dll > libsyn123-0.exports lib /DEF:libsyn123-0.def /OUT:libsyn123-0.lib /MACHINE:x64其中…

【LangServe部署流程】5 分钟部署你的 AI 服务

目录 一、LangServe简介 二、环境准备 1. 安装必要依赖 2. 编写一个 LangChain 可运行链&#xff08;Runnable&#xff09; 3. 启动 LangServe 服务 4. 启动服务 5. 使用 API 进行调用 三、可选&#xff1a;访问交互式 Swagger 文档 四、基于 LangServe 的 RAG 应用部…