【Linux】pthread多线程同步

参考文章: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.pswp.cn/pingmian/83734.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring框架学习day7--SpringWeb学习(概念与搭建配置)

SpringWeb1.SpringWeb特点2.SpringWeb运行流程3.SpringWeb组件4.搭建项目结构图&#xff1a;4.1导入jar包4.2在Web.xml配置**4.2.1配置统一拦截分发器 DispatcherServlet**4.2.2开启SpringWeb注解&#xff08;spring.xml&#xff09; 5.处理类的搭建6.SpringWeb请求流程(自己理…

业务到解决方案构想

解决方案构想的核心理解 解决方案构想是连接业务需求与技术实现的关键桥梁&#xff0c;从您描述的内容和我的理解&#xff0c;这个阶段的核心点包括&#xff1a; 核心要点解读 转化视角&#xff1a;将业务视角的需求转变为解决方案视角 业务能力探索阶段识别了"做什么&q…

jvm学习第1day jvm简介,栈溢出、堆溢出

jvm学习第1day jvm简介&#xff0c;栈溢出、堆溢出 jvm简介栈线程安全栈溢出线程运行诊断堆堆溢出 方法区方法区内存溢出常量池和运行时常量池 jvm简介 jvm 是编译后的字节码文件运行的环境&#xff0c; 因此各个平台有了jvm可以运行java.class文件&#xff0c;这是Java跨平台…

关于神经网络中的激活函数

这篇博客主要介绍一下神经网络中的激活函数以及为什么要存在激活函数。 首先&#xff0c;我先做一个简单的类比&#xff1a;激活函数的作用就像给神经网络里的 “数字信号” 加了一个 “智能阀门”&#xff0c;让机器能学会像人类一样思考复杂问题。 没有激活i函数的神经网络…

免费无限使用GPT Plus、Claude Pro、Grok Super、Deepseek满血版

渗透智能-ShirtAI&#xff0c;可以免费无限使用GPT Plus、Claude Pro、Grok Super、Deepseek满血版、除此之外还能免费使用AI搜索、Gemini AI、AI照片修复、AI橡皮擦、AI去背景、AI智能抠图、AI证件照、OCR识别、在线思维导图、在线绘图工具、PDF工具箱、PDF翻译。 传送入口&a…

阿里云 Linux 搭建邮件系统全流程及常见问题解决

阿里云 Linux 搭建 [conkl.com]邮件系统全流程及常见问题解决 目录 阿里云 Linux 搭建 [conkl.com]邮件系统全流程及常见问题解决一、前期准备&#xff08;关键配置需重点检查&#xff09;1.1 服务器与域名准备1.2 系统初始化&#xff08;必做操作&#xff09; 二、核心组件安装…

python版若依框架开发:项目结构解析

python版若依框架开发 从0起步&#xff0c;扬帆起航。 python版若依部署代码生成指南&#xff0c;迅速落地CURD&#xff01;项目结构解析 文章目录 python版若依框架开发前端后端 前端 后端

RabbitMQ 的异步化、解耦和流量削峰三大核心机制

RabbitMQ 的异步化、解耦和流量削峰三大核心机制 RabbitMQ 是解决数据库高并发问题的利器&#xff0c;通过异步化、解耦和流量削峰三大核心机制保护数据库。下面从设计思想到具体实现&#xff0c;深入剖析 RabbitMQ 应对高并发的完整方案&#xff1a; 一、数据库高并发核心痛点…

前端没有“秦始皇“,但可以做跨端的王[特殊字符]

前端各领域的 “百家争鸣” 框架之争&#xff1a;有 React、Vue、Angular 等多种框架。它们各有优缺点&#xff0c;开发者之间还存在鄙视链&#xff0c;比如 Vue 嫌 React 难用&#xff0c;React 嫌 Vue 不够灵活。样式处理&#xff1a; CSS 预处理器&#xff1a;像 Sass、Les…

Spring Boot-面试题(52)

摘要&#xff1a; 1、通俗易懂&#xff0c;适合小白 2、仅做面试复习用&#xff0c;部分来源网络&#xff0c;博文免费&#xff0c;知识无价&#xff0c;侵权请联系&#xff01; 1. 什么是 Spring Boot 框架&#xff1f; Spring Boot 是基于 Spring 框架的快速开发框架&#…

JVM——JVM中的字节码:解码Java跨平台的核心引擎

引入 在Java的技术版图中&#xff0c;字节码&#xff08;Bytecode&#xff09;是连接源代码与机器世界的黄金桥梁。当开发者写下第一行public class HelloWorld时&#xff0c;编译器便开始了一场精密的翻译工程——将人类可读的Java代码转化为JVM能够理解的字节码指令。这些由…

Java中的JSONObject详解:从基础到高级应用

Java中的JSONObject详解&#xff1a;从基础到高级应用 在当今前后端分离的架构中&#xff0c;JSONObject已成为Java开发者处理JSON数据的瑞士军刀。本文将深入解析JSONObject的核心机制与实战技巧。 一、JSONObject的本质与实现库 1.1 核心定位 JSONObject是Java中表示JSON对…

在 SpringBoot+Tomcat 环境中 线程安全问题的根本原因以及哪些变量会存在线程安全的问题。

文章目录 前言Tomcat SpringBoot单例加载结果分析多例加载&#xff1a;结果分析&#xff1a; 哪些变量存在线程安全的问题&#xff1f;线程不安全线程安全 总结 前言 本文带你去深入理解为什么在web环境中(Tomcat SpringBoot)会存在多线程的问题以及哪些变量会存在线程安全的…

npm install 相关命令

npm install 相关命令 基本安装命令 # 安装 package.json 中列出的所有依赖 npm install npm i # 简写形式# 安装特定包 npm install <package-name># 安装特定版本 npm install <package-name><version>依赖类型选项 # 安装为生产依赖&#xff08;默认&…

贪心算法应用:最小反馈顶点集问题详解

贪心算法应用&#xff1a;最小反馈顶点集问题详解 1. 问题定义与背景 1.1 反馈顶点集定义 反馈顶点集(Feedback Vertex Set, FVS)是指在一个有向图中&#xff0c;删除该集合中的所有顶点后&#xff0c;图中将不再存在任何有向环。换句话说&#xff0c;反馈顶点集是破坏图中所…

BiliNote部署实践

​ 开源地址&#xff1a; https://github.com/JefferyHcool/BiliNote &#x1f680; 快速开始 1. 克隆仓库 git clone https://github.com/JefferyHcool/BiliNote.git cd BiliNote mv .env.example .env2. 启动后端&#xff08;FastAPI&#xff09; cd backend pip insta…

用go从零构建写一个RPC(4)--gonet网络框架重构+聚集发包

在追求高性能的分布式系统中&#xff0c;RPC 框架的底层网络能力和数据传输效率起着决定性作用。经过几轮迭代优化&#xff0c;我完成了第四版本的 RPC 框架。相比以往版本&#xff0c;这一版本的最大亮点在于 重写了底层网络框架 和 实现了发送端的数据聚集机制&#xff0c;这…

MySQL 高可用基石 - 复制监控与常见 HA 方案

MySQL 高可用基石 - 复制监控与常见 HA 方案 MySQL 复制核心原理 MySQL 复制允许数据从一个 MySQL 数据库服务器(称为主库 - Primary,旧称 Master)复制到一个或多个其他的 MySQL 服务器(称为从库 - Replica,旧称 Slave)。 复制的主要目的: 高可用性 (High Availability…

微信小程序(uniapp)自定义 TabBar

微信小程序&#xff08;uniapp&#xff09;自定义 TabBar 实现指南 在微信小程序开发中&#xff0c;TabBar 是底部导航栏的重要组件&#xff0c;但官方提供的 TabBar 样式和功能较为基础&#xff0c;无法满足所有项目的需求。本文将详细介绍如何在 uniapp 中实现自定义 TabBar…

MLP实战二:MLP 实现图像数字多分类

任务 实战&#xff08;二&#xff09;&#xff1a;MLP 实现图像多分类 基于 mnist 数据集&#xff0c;建立 mlp 模型&#xff0c;实现 0-9 数字的十分类 task: 1、实现 mnist 数据载入&#xff0c;可视化图形数字&#xff1b; 2、完成数据预处理&#xff1a;图像数据维度转换与…