文章目录
- 线程的同步
- 同步:
- 条件变量:
- `pthread_cond_init()`:
- `pthread_cond_wait()`
- `pthread_cond_signal`
- pthread_cond_broadcast
- cp问题
- 伪唤醒
- 信号量
- **多线程的互斥用信号量**:
- **单线程的互斥用锁**:
线程的同步
同步:
让所有的线程获取锁,按照一定的顺序。按照一定的顺序获取资源就是同步。 (顺序性)
条件变量:
条件变量必须依赖于锁的使用
pthread_cond_init()
:
是用于初始化条件变量的函数,条件变量是多线程编程中的一种同步机制,允许线程在等待某个条件满足时进入休眠状态,并在条件满足时被唤醒。条件变量通常与互斥锁一起使用,以防止竞争条件。
函数原型:
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
参数
cond
: 指向要初始化的条件变量的指针。attr
: 指向条件变量属性对象的指针。如果传递NULL
,则使用默认属性。
返回值
- 成功时返回
0
。 - 失败时返回一个错误码。
pthread_cond_wait()
是 POSIX 线程库中用于条件变量等待的函数。它使线程进入等待状态,直到某个条件满足。在此过程中,pthread_cond_wait()
会自动释放与条件变量关联的互斥锁,并在条件变量被唤醒时重新获取该互斥锁。这种行为可以防止等待条件的线程占用互斥锁,使得其他线程可以修改条件并唤醒等待线程。
函数原型
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
cond
: 指向条件变量的指针。mutex
: 指向与条件变量关联的互斥锁的指针。
返回值
- 返回 0 表示成功。
- 返回错误码表示失败(例如:
EINVAL
表示无效的条件变量或互斥锁,EPERM
表示互斥锁未被调用线程持有)。
pthread_cond_signal
是 POSIX 线程库中用于条件变量的一个函数。它的作用是唤醒至少一个等待在指定条件变量上的线程。条件变量通常和互斥锁一起使用,以实现线程间的同步。
int pthread_cond_signal(pthread_cond_t *cond);
cond
:指向条件变量的指针(pthread_cond_t
类型)。- 返回值:如果成功返回
0
,如果失败返回错误代码。
pthread_cond_signal
唤醒一个(至少一个)等待在 cond
条件变量上的线程。如果有多个线程在这个条件变量上等待,则唤醒其中的一个线程。该函数不会确保唤醒哪个具体线程,这是由操作系统调度器决定的。
使用场景
条件变量通常用于线程之间的等待和通知机制。例如,生产者-消费者问题中,消费者可能在条件变量上等待,直到生产者生成了新数据并发出信号。
条件变量的典型使用流程
-
等待线程:通常使用
pthread_cond_wait
或pthread_cond_timedwait
函数等待某个条件。- 在调用
pthread_cond_wait
之前,必须持有与该条件变量关联的互斥锁(pthread_mutex_t
)。 - 当条件满足并从等待中返回时,线程会自动重新持有该互斥锁。
- 在调用
-
通知线程:当条件满足时,调用
pthread_cond_signal
(唤醒一个线程)或者pthread_cond_broadcast
(唤醒所有等待的线程)来通知等待的线程。
例子
以下是一个简单的生产者-消费者模型的例子,展示了如何使用 pthread_cond_signal
:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0; // 共享资源:表示是否有可供消费的数据void* consumer(void* arg) {pthread_mutex_lock(&mutex);// 等待数据准备好while (!ready) {printf("Consumer waiting for data...\n");pthread_cond_wait(&cond, &mutex); // 等待条件变量信号}printf("Consumer received signal and consumed data!\n");// 释放互斥锁pthread_mutex_unlock(&mutex);return NULL;
}void* producer(void* arg) {sleep(1); // 模拟生产者生成数据的过程pthread_mutex_lock(&mutex);ready = 1; // 生产者准备好数据printf("Producer produced data and sending signal...\n");pthread_cond_signal(&cond); // 发出条件信号,唤醒一个等待线程pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t producer_thread, consumer_thread;// 创建消费者和生产者线程pthread_create(&consumer_thread, NULL, consumer, NULL);pthread_create(&producer_thread, NULL, producer, NULL);// 等待线程完成pthread_join(consumer_thread, NULL);pthread_join(producer_thread, NULL);return 0;
}
消费者线程:进入 pthread_cond_wait
状态,等待条件变量上的信号。pthread_cond_wait
会自动释放 mutex
,使其他线程可以访问共享资源。
生产者线程:生成数据后,发出 pthread_cond_signal
信号,通知等待的消费者。
当消费者被唤醒后,它重新获取互斥锁并处理共享资源。
pthread_cond_broadcast
是 POSIX 线程库中的一个函数,用于唤醒等待在指定条件变量上的所有线程。与 pthread_cond_signal 不同,pthread_cond_signal 只唤醒一个等待的线程,而 pthread_cond_broadcast 会唤醒所有等待线程。
int pthread_cond_broadcast(pthread_cond_t *cond);
cond:指向条件变量的指针(类型为 pthread_cond_t)。
返回值:如果成功返回 0,如果失败返回错误代码。
pthread_cond_broadcast 唤醒所有当前等待在 cond 条件变量上的线程。通常用于多个线程可能需要响应某个条件变化的场景。这些线程会尝试重新获得与条件变量关联的互斥锁,并在锁定成功后继续执行。
cp问题
生产者 VS 生产者 : 竞争关系,互斥关系
生产者 VS 消费者 :互斥关系,原子性。同步。
消费者 VS 消费者 : 互斥关系
3种关系,2种角色–生产者消费者,1个消费场所–特定结构的内存空间
优点:支持忙闲不均。生产与消费进行解耦。
伪唤醒
在生产消费者模型下,队列还剩余一个被生产满,刚好这时唤醒了一批线程,其中一个抢到了锁,并将队列生产满,释放锁之后,这个锁又刚好被上次唤醒的线程抢到,又要去执行生产任务,但是队列是满的,所以就发生了伪唤醒的情况。
信号量
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。
//始化信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
//参数:
//pshared:0表示线程间共享,非零表示进程间共享
//value:信号量初始值//销毁信号量
int sem_destroy(sem_t *sem);//等待信号量
//功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()//发布信号量
//功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
int sem_post(sem_t *sem);//V()
基于环形队列的生产消费者模型:
多线程的互斥用信号量:
信号量可以用于控制多个线程对共享资源的访问,尤其是在需要限制并发数量的场合。比如,使用计数信号量可以允许一定数量的线程同时访问资源,从而实现并发控制。
单线程的互斥用锁:
锁(如互斥锁)适合于保护共享资源的独占访问,确保在任何时刻只有一个线程能够进入临界区。单线程的情况下,锁的使用更为简单和直接,因为不需要管理并发访问的复杂性。
- 信号量适用于多线程环境下的资源计数和控制,并发访问。
- 锁更适合于需要独占访问的情境,确保数据一致性。
可以允许一定数量的线程同时访问资源,从而实现并发控制。