一. 线程的概念
- 进程的上下文切换:
a.上下文:运行一个进程所需要的所有资源。
b.上下文切换:替换原有内容,从访问进程A的所有资源切换到访问进程B的所有资源,是一个耗时操作。
c.为了提高系统性能,引入了一个轻量级进程的概念,称之为线程。
1. 进程(Process)
- 定义:进程是一个正在运行的程序的实例。它是操作系统资源分配的基本单位。
- 特征:
拥有独立的内存空间:每个进程都有自己的地址空间,包括代码段、数据段和堆栈段。
独立性:一个进程的崩溃不会影响其他进程。
拥有资源:进程包含执行时需要的资源,如文件描述符、内存、设备等。
调度和切换:操作系统通过进程调度器来管理多个进程的执行,进程切换开销较大。
2. 线程(Thread)
- 定义:线程是进程内的一个执行路径。它是操作系统调度的基本单位。
特征: - 共享进程资源:同一进程内的所有线程共享进程的地址空间和资源(如文件描述符、全局变量等)。
- 独立执行流:每个线程有自己的程序计数器PC、栈和局部变量。
- 开销较小:线程切换比进程切换开销小,因为线程共享进程资源。
3. 进程与线程的关系
- 包含关系:一个进程可以包含一个或多个线程。线程是进程的一部分,一个进程至少有一个主线程。
- 资源共享:同一进程内的线程共享内存和其他资源。不同进程的资源是独立的。
- 独立性与依赖性:
- 独立性:进程是独立的,可以彼此独立运行,互不干扰。一个进程的崩溃不会影响其他进程。线程运行在进程的空间内,每一个进程至少需要一个线程作为指令执行体。
-依赖性:线程依赖于进程,同一进程内的线程会相互影响。一个线程的异常可能导致整个进程的崩溃。
4. 实际应用
- 单线程进程:例如,一个简单的命令行工具通常是单线程的,整个程序运行在一个线程中。
- 多线程进程:例如,现代的浏览器通常使用多线程来实现并发操作,提升用户体验。
二. 线程的函数
- 对于包含线程的函数编译时需要手动连接pthread库
gcc 1.c -pthread//链接库
1. pthread_create
- pthread_create 函数用于创建一个新的线程,新的线程将执行指定的函数。
函数原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
- thread_t *thread:该指针指向的内存空间存储了创建的线程的tid号;
- pthread_attr_t *attr:线程属性,一般填NULL,代表默认属性。(例如分离属性等等…)
该指针指向的空间中存储了新线程的属性,该属性需要用pthread_attr_init函数初始化。 - void *(*start_routine) (void ):函数指针,需要传入一个回调函数。该函数中存储的是新创建的线程所要执行的任务,该函数的返回值是void类型,参数列表是void*类型。
- void *arg: 传递给回调函数的参数;
- 返回值
成功,返回0;
失败,返回错误码,注意不是errno,所以无法用perror打印错误信息; - 代码示例
#include <stdio.h> // 标准输入输出头文件,用于打印输出
#include <pthread.h> // POSIX 线程库头文件,用于创建和管理线程
#include <unistd.h> // UNIX 标准头文件,提供 sleep 函数的声明
// 新创建的线程的执行体
void* callBack(void* arg) // void* arg = NULL 表示该函数可以接收任意类型的参数
{
while(1) // 无限循环,线程会一直运行
{
printf("this is other func __%d__\n", __LINE__); // 打印当前代码行号,方便调试和跟踪
sleep(1); // 线程休眠1秒,减缓输出频率
}
return NULL; // 虽然这行代码不会执行到,但函数需要返回一个指针
}
int main(int argc, const char *argv[])
{
// 创建一个线程
pthread_t tid; // 定义一个线程标识符
if(pthread_create(&tid, NULL, callBack, NULL) != 0) // 创建新线程,失败返回非0值
{
fprintf(stderr, "pthread_create failed __%d__\n", __LINE__); // 打印错误信息
return -1; // 返回-1,表示程序异常退出
}
while(1) // 无限循环,主线程会一直运行
{
printf("this is main func __%d__\n", __LINE__); // 打印当前代码行号,方便调试和跟踪
sleep(1); // 主线程休眠1秒,减缓输出频率
}
return 0; // 虽然这行代码不会执行到,但函数需要返回一个整数
}
2. 注意事项
- 从main函数进入的线程称之为主线程,新创建的其他线程称之为分支线程或者子线程。
- 在创建完毕后,主线程和分支线程谁先运行不确定。主要看cpu的调度。
- 线程是依附于进程的,如果进程退出,则该进程下的所有线程都会强制退出。
- 分支线程退出后,不会影响到其他线程运行。
3. 线程传入参数
- 定义全局变量int a=10,分支线程中修改a=20后,主线程访问a,是10还是20
访问到的是20,且访问到的是同一个变量a。因为主线程和分支线程共享附属进程的所有资源。 - 主线程定义局部变量int b=10,分支线程能否访问到。如果想要让分支线程访问该怎么办
无法直接访问,但是可以通过pthread_create的最后一个参数传参。 - 分支线程定义局部变量int c=10,主线程能否访问到。如果想要访问到该怎么办。
无法直接访问,需要通过主线程传址的方式实现。
4. pthread_exit
- 功能: 退出当前线程
- 原型:
#include<pthread.h>
void pthread_exit(void *retval);
- 参数:
void* retval: 传递线程退出状态,该参数可以被同一个进程的其他线程通过pthread_join函数接受。如果不想传递,可以填NULL;
5. pthread_join
- 功能:阻塞函数,阻塞等待指定的线程退出。接受线程退出状态值,同时回收线程的资源
- 原型
#include<pthread.h>
int pthread_join(pthread_t thread,void **retval);
-
参数
pthread_t thread:指定要等待的线程的tid号;
void **retval:该函数会将目标线程的退出状态值拷贝到retval指针指向的内存空间中,若不想接受,可以填写NULL; -
返回值
成功返回0;
失败,返回错误码,不是errno,所以无法用perror打印错误信息; -
代码示例
#include <stdio.h> // 标准输入输出头文件
#include <pthread.h> // POSIX 线程库头文件
#include <unistd.h> // UNIX 标准头文件,提供 sleep 函数的声明
#include <stdlib.h> // 标准库头文件,提供 malloc 和 free 函数的声明
// 新创建的线程的执行体
void* callBack(void* arg) // 线程函数,参数为 void* 类型
{
for(int i = 0; i < 3; i++) // 循环三次
{
printf("this is other func __%d__\n", __LINE__); // 打印当前代码行号,方便调试和跟踪
sleep(1); // 线程休眠1秒
}
// static int a = 10; // 延长 a 的生命周期
// pthread_exit((void*)&a); // 线程退出,并返回 a 的地址
int* pa = (int*)malloc(4); // 申请堆空间
*pa = 10; // 设置 pa 指向的值为 10
pthread_exit((void*)pa); // 线程退出,并返回 pa 的地址
}
int main(int argc, const char *argv[]) // 主函数,程序入口
{
// 创建一个线程
pthread_t tid; // 定义线程标识符
if(pthread_create(&tid, NULL, callBack, NULL) != 0) // 创建线程,失败返回非0值
{
fprintf(stderr, "pthread_create failed __%d__\n", __LINE__); // 打印错误信息
return -1; // 返回-1,表示程序异常退出
}
printf("this is main func __%d__\n", __LINE__); // 打印当前代码行号,方便调试和跟踪
// &ptr 指向的空间中会存储 pthread_exit 返回的值
// 即 ptr 空间中会存储 &a; ----> ptr = &a
// 即 ptr 空间中会存储 pa; ----> ptr = pa
void* ptr; // 定义一个 void* 类型的指针
pthread_join(tid, &ptr); // 阻塞等待 tid 线程退出
printf("主线程准备退出 %d\n", *(int*)ptr); // 打印线程退出时返回的值
// 释放堆空间
free(ptr); // 释放通过 malloc 分配的堆内存
return 0; // 返回 0,表示程序正常退出
}
5. pthread_detach
- 功能:用于将一个线程设置为分离状态。分离状态的线程在终止时会自动释放其资源,而不是由其他线程显式地调用 pthread_join 来回收资源。这对于那些不需要等待其完成的线程特别有用。
- 原型
#include<pthread.h>
int pthread_detach(pthread_t thread);
- 参数
pthread_t thread:指定要分离的线程的tid号; - 返回值
成功返回0;
失败返回非0; - 代码示例
#include <stdio.h> // 标准输入输出头文件,用于打印输出
#include <stdlib.h> // 标准库头文件,用于动态内存分配
#include <pthread.h> // POSIX 线程库头文件,用于创建和管理线程
#include <unistd.h> // UNIX 标准头文件,提供 sleep 函数的声明
void* threadFunc(void* arg) { // 线程函数,接受一个 void* 类型的参数
int num = *((int*)arg); // 将参数转换为 int* 类型,并解引用以获取线程编号
printf("Thread %d: started\n", num); // 打印线程开始的消息
sleep(2); // 使线程休眠2秒
printf("Thread %d: finished\n", num); // 打印线程结束的消息
return NULL; // 返回 NULL,表示线程终止
}
int main() { // 主函数,程序入口
pthread_t tid; // 定义线程标识符
int thread_num = 1; // 定义线程编号,并初始化为1
// 创建一个线程
if (pthread_create(&tid, NULL, threadFunc, &thread_num) != 0) { // 创建线程,传递线程标识符指针、默认属性(NULL)、线程函数和参数
fprintf(stderr, "Error creating thread\n"); // 如果创建线程失败,打印错误信息
return 1; // 返回 1,表示程序异常退出
}
// 将线程设置为分离状态
if (pthread_detach(tid) != 0) { // 调用 pthread_detach 将线程设置为分离状态
fprintf(stderr, "Error detaching thread\n"); // 如果分离线程失败,打印错误信息
return 1; // 返回 1,表示程序异常退出
}
printf("Main thread: thread %d detached\n", thread_num); // 打印主线程分离线程的消息
// 主线程继续执行
sleep(3); // 主线程休眠3秒
printf("Main thread: exiting\n"); // 打印主线程退出的消息
return 0; // 返回 0,表示程序正常退出
}
6.pthread_cancel
- 功能
- pthread_cancel 向指定的线程发送一个取消请求。该线程在到达一个取消点时会检查这个请求并响应。
- 取消请求并不立即生效,取消请求是延迟取消(PTHREAD_CANCEL_DEFERRED),这意味着线程将在其到达下一个取消点时进行响应。 - 返回值:
- 返回0表示取消请求已成功发送给线程。
- 返回错误码表示取消请求失败。非0 - 函数原型
#include<pthread.h>
int pthread_cancel(pthread_t thread);
参数:
thread 是指定要请求退出的线程。
7. pthread_self
- 功能:获取调用线程的线程ID
- 原型:
#include <pthread.h>
pthread_t pthread_self(void);
范例
void* thread_func(void* arg) {
printf("Thread ID: %lu\n", pthread_self());
return NULL;
}
三. 线程的同步互斥机制
- 线程同步和互斥是并发编程中确保多个线程安全访问共享资源的关键机制。
同步互斥机制
互斥锁
- 互斥锁(Mutex,Mutual Exclusion)是并发编程中用于确保多个线程互斥地访问共享资源的一种机制。互斥锁的主要目的是防止多个线程同时修改共享资源,避免出现竞态条件(Race Condition),从而保证数据的一致性和正确性。
- 互斥锁的主要特性
- 互斥性(Exclusion):
一次只有一个线程能持有互斥锁并访问共享资源。如果其他线程尝试获取该锁,它们将进入阻塞状态,直到持有锁的线程释放锁。 - 阻塞(Blocking):
当一个线程请求已经被其他线程持有的互斥锁时,它会进入阻塞状态,直到锁被释放。这种机制确保共享资源不会同时被多个线程访问。 - 解锁(Unlock):
持有锁的线程在完成对共享资源的访问后,需要显式释放互斥锁,以便其他阻塞的线程可以继续执行。
- 互斥锁函数
- pthread_mutex_init (一般不用,除非修改属性)
功能:创建并初始化互斥锁
原型: #include<stdio.h>
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
参数: pthread_mutex_t *mutex---------->该指针指向的内存空间,会存储申请到的互斥锁
const pthread_mutexattr_t *mutexattr:指向互斥锁属性对象的指针。如果使用默认属性,可以传递 NULL。
返回值
如果成功初始化锁,返回 0。
如果失败,返回错误码。例如,如果参数无效,会返回 EINVAL;如果系统资源不足,会返回 ENOMEM。
- pthread_mutex_lock
原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:用于尝试获取由 pthread_mutex_t 类型定义的互斥锁。如果互斥锁当前没有被其他线程持有,那么调用该函数的线程将成功获取锁并继续执行。如果互斥锁已经被其他线程持有,则调用该函数的线程将被阻塞,直到互斥锁被释放为止。
返回值:如果成功获取锁,返回 0。
如果失败,返回错误码。例如,如果参数无效,会返回 EINVAL;如果发生死锁,会返回 EDEADLK。
- pthread_mutex_unlock
功能:pthread_mutex_unlock 用于释放一个互斥锁,使得其他等待该锁的线程可以获取锁并继续执行。调用该函数的线程必须持有锁,否则调用的结果是未定义的。
原型: int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:如果成功释放锁,返回 0。
如果失败,返回错误码。例如,如果参数无效,会返回 EINVAL;如果互斥锁当前未被锁定,则返回 EPERM。
注意事项:如果同一个线程多次加锁(在递归锁的情况下),那么必须相应次数的解锁。普通的互斥锁不允许同一线程多次加锁。
代码示例
#include <stdio.h>
#include <pthread.h>
// 共享资源
int counter = 0;
// 互斥锁
pthread_mutex_t lock;
void* increment_counter(void* arg) {
for (int i = 0; i < 1000000; ++i) {
// 加锁
pthread_mutex_lock(&lock);
// 访问和修改共享资源
++counter;
// 解锁
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main() {
// 初始化互斥锁
pthread_mutex_init(&lock, NULL);
pthread_t t1, t2;
// 创建线程
pthread_create(&t1, NULL, increment_counter, NULL);
pthread_create(&t2, NULL, increment_counter, NULL);
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&lock);
printf("Final counter value: %d\n", counter);
return 0;
}
- pthread_mutex_destroy
功能:pthread_mutex_destroy 用于销毁一个已经初始化的互斥锁。销毁互斥锁之前,必须确保没有任何线程持有该锁,并且没有线程正在等待获取该锁。
原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
返回值:如果成功销毁锁,返回 0。
如果失败,返回错误码。例如,如果参数无效,会返回 EINVAL;如果互斥锁仍然被某个线程持有,行为是未定义的。
代码示例
#include <stdio.h>
#include <pthread.h>
// 共享资源
int counter = 0;
// 互斥锁
pthread_mutex_t lock;
void* increment_counter(void* arg) {
for (int i = 0; i < 1000000; ++i) {
// 加锁
pthread_mutex_lock(&lock);
// 访问和修改共享资源
++counter;
// 解锁
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main() {
// 初始化互斥锁
pthread_mutex_init(&lock, NULL);
pthread_t t1, t2;
// 创建线程
pthread_create(&t1, NULL, increment_counter, NULL);
pthread_create(&t2, NULL, increment_counter, NULL);
// 等待线程结束
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&lock);
printf("Final counter value: %d\n", counter);
return 0;
}
四.无名信号量(信号灯)
- 主要作用是用于控制并发环境中对共享资源的访问。通过无名信号量,程序可以在多线程或多进程场景中实现同步,避免资源竞争、数据不一致或死锁等问题。
- 简单而言作用有:互斥访问,顺序控制,控制资源访问数量,管理生产者-消费者模型,避免数据竞争,防止死锁。
1. 主要函数
- sem_init
原型:
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数说明:
sem:指向信号量的指针。
pshared:如果为0,信号量在进程内的线程间共享;如果不为0,信号量在多个进程间共享。
value:信号量的初始值。
返回值:成功返回0,失败返回-1。
- sem_wait(P操作)
原型:
int sem_wait(sem_t *sem);
参数:
sem:指向要操作的信号量的指针。
返回值:成功返回0,失败返回-1
注意事项:
如果信号量已经是0,调用sem_wait会导致调用线程阻塞,直到信号量的值大于0。
- sem_post(V操作)
原型:
int sem_post(sem_t *sem);
参数:
sem:指向要操作的信号量的指针
返回值:成功返回0,失败返回-1。
如果有线程阻塞在sem_wait调用上,其中一个会被唤醒。
- sem_destroy
原型:
int sem_destroy(sem_t *sem);
参数:
sem:指向要销毁的信号量的指针
返回值:成功返回0,失败返回-1。
在调用sem_destroy之前,确保没有线程在等待信号量,否则可能导致不可预知的行为。
- 代码举例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t sem; // 定义无名信号量
void* thread_func(void* arg) {
// 使用 sem_wait 来进入临界区
sem_wait(&sem);
printf("Thread %ld is in critical section.\n", (long)arg);
sleep(1); // 模拟临界区操作
printf("Thread %ld is leaving critical section.\n", (long)arg);
// 使用 sem_post 来离开临界区
sem_post(&sem);
return NULL;
}
int main() {
pthread_t threads[3];
// 初始化信号量,初始值为1,线程间共享
if (sem_init(&sem, 0, 1) != 0) {
perror("sem_init failed");
return EXIT_FAILURE;
}
// 创建三个线程
for (long i = 0; i < 3; i++) {
if (pthread_create(&threads[i], NULL, thread_func, (void*)i) != 0) {
perror("pthread_create failed");
return EXIT_FAILURE;
}
}
// 等待线程完成
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
// 销毁信号量
if (sem_destroy(&sem) != 0) {
perror("sem_destroy failed");
return EXIT_FAILURE;
}
return 0;
}
五.条件变量
- 条件变量(Condition Variable)是一种同步机制,允许线程在某些条件满足之前进入等待状态,从而实现线程之间的协调。条件变量通常与互斥锁一起使用,以避免竞态条件和确保共享资源的正确访问。
1.条件变量函数
- pthread_cond_init
功能:初始化一个条件变量。
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
参数说明:
cond:指向条件变量的指针。
attr:指向条件变量属性的指针,通常为NULL以使用默认属性。
返回值:成功返回0,失败返回错误码。
- pthread_cond_wait
功能:将调用线程放入等待队列,并释放关联的互斥锁(mutex),使得其他线程能够获取该互斥锁并修改共享资源。在被唤醒时,该函数会重新获取互斥锁,然后继续执行。
原型:int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex)
参数说明:
cond:指向条件变量的指针。
mutex:指向互斥锁的指针,该互斥锁必须已被当前线程锁定。
返回值
如果成功,返回 0。
如果失败,返回错误码。例如,如果参数无效,会返回 EINVAL。
- pthread_cond_signal
功能:唤醒等待该条件变量的一个线程。
原型:
int pthread_cond_signal(pthread_cond_t *cond);
参数说明:
cond:指向条件变量的指针。
返回值:成功返回0,失败返回错误码。
- pthread_cond_broadcast
功能:唤醒等待该条件变量的所有线程。
原型:
int pthread_cond_broadcast(pthread_cond_t *cond);
参数说明:
cond:指向条件变量的指针。
返回值:成功返回0,失败返回错误码。
- pthread_cond_destroy
功能:销毁一个条件变量。
int pthread_cond_destroy(pthread_cond_t *cond);
参数说明:
cond:指向要销毁的条件变量的指针。
返回值:成功返回0,失败返回错误码。
- 代码举例
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#define BUFFER_SIZE 10 // 定义缓冲区的大小
int buffer[BUFFER_SIZE]; // 定义用于存储数据的缓冲区
int count = 0; // 用于跟踪缓冲区中当前存储的项目数量
// 初始化互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 用于保护缓冲区的互斥锁
pthread_cond_t cond_full = PTHREAD_COND_INITIALIZER; // 缓冲区满的条件变量
pthread_cond_t cond_empty = PTHREAD_COND_INITIALIZER; // 缓冲区空的条件变量
void* producer(void* arg) {
for (int i = 0; i < 20; i++) { // 生产20个项目
pthread_mutex_lock(&mutex); // 加锁互斥量以保护共享数据
while (count == BUFFER_SIZE) { // 如果缓冲区已满
pthread_cond_wait(&cond_full, &mutex); // 等待缓冲区有空位,释放互斥锁并阻塞
}
// 生产数据并放入缓冲区
buffer[count] = i; // 将生产的项目放入缓冲区
printf("Produced: %d\n", i); // 打印生产的项目编号
count++; // 增加缓冲区中的项目计数
// 通知消费者有新的项目可用
pthread_cond_signal(&cond_empty); // 唤醒等待缓冲区不为空的消费者
pthread_mutex_unlock(&mutex); // 解锁互斥量
sleep(1); // 模拟生产者的生产过程
}
return NULL; // 线程函数需要返回NULL
}
void* consumer(void* arg) {
for (int i = 0; i < 20; i++) { // 消费20个项目
pthread_mutex_lock(&mutex); // 加锁互斥量以保护共享数据
while (count == 0) { // 如果缓冲区为空
pthread_cond_wait(&cond_empty, &mutex); // 等待缓冲区有数据可消费,释放互斥锁并阻塞
}
// 从缓冲区消费数据
int data = buffer[count - 1]; // 从缓冲区读取最后一个项目
printf("Consumed: %d\n", data); // 打印消费的项目编号
count--; // 减少缓冲区中的项目计数
// 通知生产者有新的空位可用
pthread_cond_signal(&cond_full); // 唤醒等待缓冲区不满的生产者
pthread_mutex_unlock(&mutex); // 解锁互斥量
sleep(1); // 模拟消费者的消费过程
}
return NULL; // 线程函数需要返回NULL
}
int main() {
pthread_t producer_thread, consumer_thread; // 声明生产者和消费者线程
// 创建生产者线程
pthread_create(&producer_thread, NULL, producer, NULL);
// 创建消费者线程
pthread_create(&consumer_thread, NULL, consumer, NULL);
// 等待生产者线程完成
pthread_join(producer_thread, NULL);
// 等待消费者线程完成
pthread_join(consumer_thread, NULL);
// 销毁条件变量和互斥锁
pthread_cond_destroy(&cond_full); // 销毁满条件变量
pthread_cond_destroy(&cond_empty); // 销毁空条件变量
pthread_mutex_destroy(&mutex); // 销毁互斥锁
return 0; // 返回0表示程序正常结束
}
注意事项
- 关联互斥锁:
每个条件变量必须与一个互斥锁关联。在调用 pthread_cond_wait 前,线程必须持有该互斥锁。 - 避免虚假唤醒:
由于虚假唤醒的存在,等待条件时应总是使用 while 循环检查条件,而不是 if 条件。 - 正确的解锁和等待顺序:
pthread_cond_wait 会在等待条件时自动释放互斥锁,并在被唤醒时重新获取锁。
tips
-
pthread_mutex_t *mutex;就是定义pthread_mutex_t mutex; 然后取地址。
-
信号量和条件变量的区别
1. 信号量:用于控制多个相同资源的访问数量,可以限制同时访问的线程数。 条件变量:用于等待和通知特定条件的变化,**不用于**计数。 2. 信号量:更适合用于控制资源访问的数量,如访问共享资源的线程数限制。 条件变量:更适合用于等待某个条件的变化,如实现复杂的线程同步逻辑。 3. 信号量:可以用于进程间或线程间同步。 条件变量:通常用于线程间同步,依赖互斥锁来保护条件。