线程同步是指多个线程协调工作,以便在共享资源的访问和操作过程中保持数据一致性和正确性。在多线程环境中,线程是并发执行的,因此如果多个线程同时访问和修改共享资源,可能会导致数据不一致、竞态条件(race condition)等问题。线程同步通过协调线程的执行顺序和共享资源的访问来避免这些问题。
在多线程编程中,需要线程同步的主要原因包括:
共享资源的安全访问:多个线程可能同时访问和修改共享的数据或资源,如果没有同步机制,可能会导致数据不一致或损坏。
避免竞态条件:竞态条件是指多个线程以不受控制的顺序访问共享资源,从而导致程序执行结果不确定的情况。通过同步机制可以避免竞态条件的发生。
保证程序正确性:线程同步确保多线程程序的行为是可预测和正确的,避免因并发访问而引入的错误。
互斥量(互斥锁)是一种最基本的同步机制,用于保护临界区(Critical Section),确保在同一时间只有一个线程可以访问共享资源。

它提供了两个主要操作:
通过这种机制,互斥量确保了临界区中的代码在同一时间只能被一个线程执行,从而避免了数据竞争和不一致的问题。
初始化互斥量,并可选地设置其属性。
- int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
- 参数:
- pthread_mutex_t *mutex:指向互斥量变量的指针,用于初始化该互斥量。
- const pthread_mutexattr_t *attr:可选参数,指向 pthread_mutexattr_t 类型的指针,用于设置互斥量的属性。如果为 NULL,使用默认属性。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号(例如 EINVAL 表示参数无效)。
pthread_mutex_destroy 函数来释放其占用的资源。1.1.2pthread_mutex_destroy()
销毁互斥量,释放其占用的资源。
- int pthread_mutex_destroy(pthread_mutex_t *mutex);
- 参数:
- pthread_mutex_t *mutex:指向要销毁的互斥量变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
加锁操作,阻塞当前线程直到获取锁。
- int pthread_mutex_lock(pthread_mutex_t *mutex);
- 参数:
- pthread_mutex_t *mutex:指向要加锁的互斥量变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
pthread_mutex_lock 和 pthread_mutex_unlock 来保护临界区。尝试加锁操作,非阻塞式,立即返回结果。
- int pthread_mutex_trylock(pthread_mutex_t *mutex);
- 参数:
- pthread_mutex_t *mutex:指向要加锁的互斥量变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回 EBUSY(互斥量已被锁定)或其他错误号。
解锁操作,释放互斥量。
- int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 参数:
- pthread_mutex_t *mutex:指向要解锁的互斥量变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
pthread_mutex_lock 后调用 pthread_mutex_unlock 来释放锁。创建两个线程来共享一个全局变量 int number,然后每个线程分别对其进行5000次递增操作,同时使用互斥量来保证线程同步。(如果没有锁大家可以看看会发生什么情况)
- #include
- #include
- #include
-
- #define NUM_THREADS 2
- #define NUM_INCREMENTS 5000
-
- int number = 0;
- pthread_mutex_t mutex; // 互斥量变量
-
- void* thread_function(void* arg) {
- int thread_id = *(int*)arg;
-
- for (int i = 0; i < NUM_INCREMENTS; ++i) {
- // 加锁
- pthread_mutex_lock(&mutex);
-
- // 临界区:对共享变量 number 执行操作
- number++;
-
- // 解锁
- pthread_mutex_unlock(&mutex);
- }
-
- pthread_exit(NULL);
- }
-
- int main() {
- pthread_t threads[NUM_THREADS];
- int thread_ids[NUM_THREADS];
-
- // 初始化互斥量
- if (pthread_mutex_init(&mutex, NULL) != 0) {
- fprintf(stderr, "Mutex initialization failed\n");
- return 1;
- }
-
- // 创建两个线程
- for (int i = 0; i < NUM_THREADS; ++i) {
- thread_ids[i] = i;
- if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]) != 0) {
- fprintf(stderr, "Thread creation failed\n");
- return 1;
- }
- }
-
- // 等待所有线程结束
- for (int i = 0; i < NUM_THREADS; ++i) {
- pthread_join(threads[i], NULL);
- }
-
- // 销毁互斥量
- pthread_mutex_destroy(&mutex);
-
- // 输出最终的 number 值
- printf("Final value of number: %d\n", number);
-
- return 0;
- }
死锁并不是linux提供给用户的一种使用方法,而是由于用户使用互斥锁不当引起的一种现象。死锁发生在多个并发执行的线程(或进程)之间,主要由于彼此竞争资源而造成。典型的死锁情况涉及两个或多个线程或进程,每个都在等待对方释放其持有的资源,导致所有线程都被阻塞,无法继续执行下去。
常见的死锁有两种:
第一种:自己锁自己,如下图代码片段

第二种 线程A拥有A锁,请求获得B锁;线程B拥有B锁,请求获得A锁,这样造成线程A和线程B都不释放自己的锁,而且还想得到对方的锁,从而产生死锁,如下图所示:

条件变量(Condition Variable) 是一种线程间同步的机制,通常与互斥锁结合使用,用于在某个条件满足时通知其他线程。条件变量允许线程在等待某个特定条件的同时阻塞,直到另一个线程显式地通知条件已经满足或者超时。条件本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
主要作用包括:
初始化条件变量 cond。
- int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
- 参数:
- pthread_cond_t *restrict cond:指向要初始化的条件变量的指针。
- const pthread_condattr_t *restrict attr:可选参数,指向 pthread_condattr_t 类型的指针,用于设置条件变量的属性。通常可以设置为 NULL,表示使用默认属性。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号(例如 EINVAL 表示参数无效)。
pthread_cond_destroy 函数来释放其占用的资源。销毁条件变量 cond,释放其占用的资源。
- int pthread_cond_destroy(pthread_cond_t *cond);
- 参数:
- pthread_cond_t *cond:指向要销毁的条件变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
阻塞当前线程,等待条件变量 cond 被其他线程信号唤醒。
- int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 参数:
- pthread_cond_t *restrict cond:指向要等待的条件变量的指针。
- pthread_mutex_t *restrict mutex:与条件变量相关联的互斥锁,用于避免竞态条件。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
mutex 锁,函数内部会自动释放 mutex 锁,并在等待期间阻塞当前线程。mutex 锁,并从函数返回。在指定的超时时间内阻塞当前线程,等待条件变量 cond 被其他线程信号唤醒。
- int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
- 参数:
- pthread_cond_t *restrict cond:指向要等待的条件变量的指针。
- pthread_mutex_t *restrict mutex:与条件变量相关联的互斥锁。
- const struct timespec *restrict abstime:指定的超时时间,为绝对时间。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
pthread_cond_wait 类似,但可以设置超时时间,在超时之后会自动返回并解除阻塞。NULL,则函数将无限期地等待,直到条件变量被信号唤醒。唤醒等待在条件变量 cond 上的一个线程。
- int pthread_cond_signal(pthread_cond_t *cond);
- 参数:
- pthread_cond_t *cond:指向要唤醒的条件变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
读写锁允许多个线程同时对共享资源进行读取操作,但是写操作时需要排他性,即同一时刻只能有一个线程进行写操作。这种区分读和写的方式能够有效地提高系统的并发性能,特别适用于数据结构中读操作远远多于写操作的情况。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。
读写锁特性:
初始化读写锁 rwlock,可以选择性地设置其属性。
- int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
- 参数:
- pthread_rwlock_t *restrict rwlock:指向要初始化的读写锁变量的指针。
- const pthread_rwlockattr_t *restrict attr:可选参数,指向 pthread_rwlockattr_t 类型的指针,用于设置读写锁的属性。通常可以设置为 NULL,表示使用默认属性。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号(例如 EINVAL 表示参数无效)。
pthread_rwlock_destroy 函数来释放其占用的资源。销毁读写锁 rwlock,释放其占用的资源。
- int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
- 参数:
- pthread_rwlock_t *rwlock:指向要销毁的读写锁变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
加读锁,允许多个线程同时对共享资源进行读取操作。
- int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
- 参数:
- pthread_rwlock_t *rwlock:指向要加读锁的读写锁变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
加写锁,确保只有一个线程可以对共享资源进行写操作,期间禁止其他线程的读或写操作。
- int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
- 参数:
- pthread_rwlock_t *rwlock:指向要加写锁的读写锁变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
尝试加读锁,非阻塞方式。如果不能立即获得锁,则立即返回。
- int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
- 参数:
- pthread_rwlock_t *rwlock:指向要加读锁的读写锁变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回 EBUSY(读写锁已被写锁占用)或其他错误号。
尝试加写锁,非阻塞方式。如果不能立即获得锁,则立即返回。
- int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
- 参数:
- pthread_rwlock_t *rwlock:指向要加写锁的读写锁变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回 EBUSY(读写锁已被其他线程占用)或其他错误号。
3.2.5pthread_rwlock_unlock()
解锁操作,释放之前加的读锁或写锁。
- int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
- 参数:
- pthread_rwlock_t *rwlock:指向要解锁的读写锁变量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回错误号。
pthread_rwlock_rdlock 或 pthread_rwlock_wrlock 后调用该函数来释放锁。其中包括3个写线程和5个读线程对同一个全局资源进行操作。每个线程都会不定时地访问和修改这个全局资源,并通过读写锁确保线程之间的同步。
- #include
- #include
- #include
- #include
// 用于随机睡眠时间 -
- #define NUM_READERS 5
- #define NUM_WRITERS 3
-
- pthread_rwlock_t rwlock;
- int global_resource = 0;
-
- void* writer(void* arg) {
- int thread_id = *(int*)arg;
- while (1) {
- // 模拟写操作
- sleep(rand() % 3); // 随机睡眠时间
-
- // 加写锁
- pthread_rwlock_wrlock(&rwlock);
-
- // 写操作
- global_resource++;
- printf("Writer %d writes: global_resource = %d\n", thread_id, global_resource);
-
- // 解锁
- pthread_rwlock_unlock(&rwlock);
- }
- return NULL;
- }
-
- void* reader(void* arg) {
- int thread_id = *(int*)arg;
- while (1) {
- // 模拟读操作
- sleep(rand() % 2); // 随机睡眠时间
-
- // 加读锁
- pthread_rwlock_rdlock(&rwlock);
-
- // 读操作
- printf("Reader %d reads: global_resource = %d\n", thread_id, global_resource);
-
- // 解锁
- pthread_rwlock_unlock(&rwlock);
- }
- return NULL;
- }
-
- int main() {
- pthread_t writers[NUM_WRITERS], readers[NUM_READERS];
- int writer_ids[NUM_WRITERS], reader_ids[NUM_READERS];
- int i;
-
- // 初始化读写锁
- pthread_rwlock_init(&rwlock, NULL);
-
- // 创建写线程
- for (i = 0; i < NUM_WRITERS; ++i) {
- writer_ids[i] = i + 1;
- pthread_create(&writers[i], NULL, writer, &writer_ids[i]);
- }
-
- // 创建读线程
- for (i = 0; i < NUM_READERS; ++i) {
- reader_ids[i] = i + 1;
- pthread_create(&readers[i], NULL, reader, &reader_ids[i]);
- }
-
- // 等待所有写线程结束
- for (i = 0; i < NUM_WRITERS; ++i) {
- pthread_join(writers[i], NULL);
- }
-
- // 等待所有读线程结束
- for (i = 0; i < NUM_READERS; ++i) {
- pthread_join(readers[i], NULL);
- }
-
- // 销毁读写锁
- pthread_rwlock_destroy(&rwlock);
-
- return 0;
- }
信号量是由一个整型变量和相关的操作集合组成,用于控制对共享资源的访问。信号量可以看作是一个计数器,用于表示可用的资源数量,线程或进程在访问资源前必须首先获取信号量,访问结束后释放信号量。
主要作用包括:
初始化一个信号量 sem。
- int sem_init(sem_t *sem, int pshared, unsigned int value);
- 参数:
- sem_t *sem:指向要初始化的信号量的指针。
- int pshared:指定信号量是进程共享(非零)还是线程共享(零)。
- unsigned int value:信号量的初始值,表示可用资源的数量。
- 返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置 errno 来指示错误原因。
sem_destroy 函数进行清理。pshared 是非零,则信号量可以在多个进程间共享,通常使用在进程间通信(IPC)的场景中。销毁一个已经初始化的信号量 sem。释放由信号量占用的资源,确保在信号量不再需要时调用。
- int sem_destroy(sem_t *sem);
- 参数:
- sem_t *sem:指向要销毁的信号量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置 errno 来指示错误原因。
对信号量 sem 进行 P 操作(等待操作)。
- int sem_wait(sem_t *sem);
- 参数:
- sem_t *sem:指向要操作的信号量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置 errno 来指示错误原因。
0,将其递减;否则阻塞当前线程,直到信号量的值大于 0。尝试对信号量 sem 进行 P 操作的非阻塞版本。与 sem_wait 不同的是,如果信号量的值为 0,立即返回而不阻塞线程。
- int sem_trywait(sem_t *sem);
- 参数:
- sem_t *sem:指向要操作的信号量的指针。
- 返回值:
- 成功:返回 0。
- 如果信号量的值为 0,表示资源不可用,立即返回 -1(不阻塞),并设置 errno 为 EAGAIN。
- 其他失败情况返回 -1,并设置 errno 来指示错误原因。
对信号量 sem 进行 V 操作(释放操作)。
- int sem_post(sem_t *sem);
- 参数:
- sem_t *sem:指向要操作的信号量的指针。
- 返回值:
- 成功:返回 0。
- 失败:返回 -1,并设置 errno 来指示错误原因。
获取信号量 sem 的当前值。sem_getvalue 函数可以获取信号量的当前值,而无需对其进行修改。
- int sem_getvalue(sem_t *restrict sem, int *restrict sval);
- 参数:
- sem_t *restrict sem:指向要获取值的信号量的指针。
- int *restrict sval:用于存储信号量当前值的整型指针。
- 返回值:
- 成功:返回 0,并将当前信号量的值存储在 sval 中。
- 失败:返回 -1,并设置 errno 来指示错误原因。
信号量演示代码:
- #include
- #include
- #include
- #include
- #include
-
- #define NUM_THREADS 5
-
- int shared_variable = 0; // 共享的变量
- sem_t mutex; // 互斥信号量
-
- void *thread_function(void *arg) {
- int thread_id = *((int *)arg);
- int local_variable = 0;
-
- while (1) {
- // 临界区开始
- sem_wait(&mutex); // 等待互斥信号量,保证互斥访问共享资源
- local_variable = shared_variable; // 读取共享变量到本地变量
- local_variable++; // 修改本地变量
- sleep(rand() % 2); // 模拟其他操作
- shared_variable = local_variable; // 写回共享变量
- printf("线程 %d 执行后,共享变量的值为: %d\n", thread_id, shared_variable);
- sem_post(&mutex); // 释放互斥信号量
- // 临界区结束
-
- sleep(rand() % 2 + 1); // 模拟一段时间后再次操作
- }
-
- pthread_exit(NULL);
- }
-
- int main() {
- pthread_t threads[NUM_THREADS];
- int thread_ids[NUM_THREADS];
-
- // 初始化互斥信号量
- sem_init(&mutex, 0, 1); // 初始值为1,表示资源未被占用
-
- // 创建线程
- for (int i = 0; i < NUM_THREADS; ++i) {
- thread_ids[i] = i;
- pthread_create(&threads[i], NULL, thread_function, (void *)&thread_ids[i]);
- }
-
- // 等待线程结束
- for (int i = 0; i < NUM_THREADS; ++i) {
- pthread_join(threads[i], NULL);
- }
-
- // 销毁信号量
- sem_destroy(&mutex);
-
- return 0;
- }