参考文章:https://blog.csdn.net/Alkaid2000/article/details/128121066
一、线程同步
-
线程的主要优势在于,能够通过全局变量来共享信息。不过,这种便携的共享是有代价的;必须确保多个线程不会同时修改同一变量,或者某一线程不会读取正在由其他线程修改的变量。(否则会出现数据安全现象)
-
临界区是指访问某一共享资源的代码片段,并且这段代码的执行应为原子操作,也就是同时访问同一共享资源的其他线程不应中断该片段的执行。
-
线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到线程完成操作,其他线程才能对内存地址进行操作,而其他线程则处于等待状态,肯定会影响效率,但是起码他只是影响了代码的一小部分。
二、互斥锁
-
为避免线程更新共享变量时出现问题,可以使用互斥量(mutex——mutual exclusion)来确保同时仅有一个线程可以访问某项共享资源。可以使用互斥量来保证对任意共享资源的原子访问。
-
互斥量有两种状态:已锁定(locked)和未锁定(unlocked)。任何时候,至多只有一个线程可以锁定该互斥量。试图对已经锁定的某一互斥量再次加锁,将可能阻塞线程或报错失败,具体取决于加锁时使用的方法。
-
一旦线程锁定互斥量,随即称为该互斥量的所有者,只有所有者才能给互斥量解锁。一般情况下,对每一共享资源(可能由多个相关变量组成)会使用不同的互斥量,每一线程在访问同一资源时将采用如下协议:
- 针对共享资源锁定互斥量。
- 访问共享资源。
- 对互斥量解锁。
-
如果多个线程试图执行这一块代码(一个临界区),事实上只有一个线程能够持有该互斥量(其他线程将遭到阻塞),即同时只有一个线程能够进入这段代码区域。
- 关于互斥量有如下的操作函数,与线程属性相似,它具有一个互斥量类型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;
}
如何避免死锁
当两个线程为了保护两个不同的共享资源而使用了两个互斥锁,那么这两个互斥锁应用不当的时候,可能会造成两个线程都在等待对方释放锁,在没有外力的作用下,这些线程会一直相互等待,就没办法继续运行,这种情况就是发生了死锁,那么死锁只有同时满足以下四个条件才会发生:
- 互斥条件。
- 持有并等待条件。
- 不可剥夺条件。
- 环路等待条件。
互斥条件:对于互斥条件是指多个线程不能同时同一个资源:如果线程 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
:超时时间(绝对时间)。
- 执行流程:
- 原子性地释放
mutex
并阻塞当前线程。 - 被唤醒时,重新获取
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
存储当前信号量值。 - 失败:返回错误码。
- 成功:返回0,
- 注意:
- 返回值可能在调用后立即被其他线程修改,仅供调试参考。
6.3.5 运行结果
七、多线程同步机制对比
机制 | 核心功能 | 关键API | 适用场景 |
---|---|---|---|
互斥锁 | 保证互斥访问 | lock , unlock | 所有互斥场景 |
读写锁 | 多读单写优化 | rdlock , wrlock , unlock | 读多写少场景 |
条件变量 | 线程间条件同步 | wait , signal , broadcast | 生产者-消费者、任务通知等 |
信号量 | 控制有限资源的并发访问 | wait , post | 连接池、线程池等资源管理 |
更多资料:https://github.com/0voice