在多线程编程里,若要强制两个线程按特定次序访问相同内存区域,可借助多种同步机制达成
在多线程编程里,若要强制两个线程按特定次序访问相同内存区域,可借助多种同步机制达成。下面介绍几种常见方法,并给出 C 语言示例代码。
- 使用互斥锁和条件变量
互斥锁用于保证同一时刻仅有一个线程能访问共享资源,条件变量用于线程间的同步通信。一个线程完成操作后,借助条件变量通知另一个线程继续执行。
#include <stdio.h>
#include <pthread.h>
#include <assert.h>// 定义互斥锁和条件变量
pthread_mutex_t mutex;
pthread_cond_t cond;
int turn = 0;// 共享内存区域
int shared_memory = 0;// 第一个线程函数
void *thread1(void *arg) {// 加锁pthread_mutex_lock(&mutex);// 如果turn!=0,进入等待状态,同时释放锁while (turn != 0) {pthread_cond_wait(&cond, &mutex);}// 访问共享内存shared_memory = 10;printf("Thread 1 accessed shared memory: %d\n", shared_memory);// 改变轮次turn = 1;// 通知另一个线程pthread_cond_signal(&cond);// 解锁pthread_mutex_unlock(&mutex);return NULL;
}// 第二个线程函数
void *thread2(void *arg) {// 加锁pthread_mutex_lock(&mutex);// 如果turn!=1,进入等待状态,同时释放锁while (turn != 1) {pthread_cond_wait(&cond, &mutex);}// 访问共享内存shared_memory += 20;printf("Thread 2 accessed shared memory: %d\n", shared_memory);// 改变轮次turn = 0;// 通知另一个线程pthread_cond_signal(&cond);// 解锁pthread_mutex_unlock(&mutex);return NULL;
}int main() {// 初始化互斥锁和条件变量int mutex_ret = pthread_mutex_init(&mutex, NULL);int cond_ret = pthread_cond_init(&cond, NULL);assert(mutex_ret == 0 && cond_ret == 0);pthread_t t1, t2;// 创建线程int create_ret1 = pthread_create(&t1, NULL, thread1, NULL);int create_ret2 = pthread_create(&t2, NULL, thread2, NULL);assert(create_ret1 == 0 && create_ret2 == 0);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁互斥锁和条件变量pthread_mutex_destroy(&mutex);pthread_cond_destroy(&cond);return 0;
}
输出:
Thread 1 accessed shared memory: 10
Thread 2 accessed shared memory: 30
- 使用原子操作和内存顺序
借助原子操作和合适的内存顺序,能保证线程按特定次序访问共享内存。
#include <stdatomic.h>
#include <pthread.h>
#include <stdio.h>atomic_int flag = ATOMIC_VAR_INIT(0);
int shared_memory = 0;// 第一个线程函数
void *thread1(void *arg) {// 访问共享内存shared_memory = 10;printf("Thread 1 accessed shared memory: %d\n", shared_memory);// 释放操作,保证前面的写操作完成atomic_store_explicit(&flag, 1, memory_order_release);return NULL;
}// 第二个线程函数
void *thread2(void *arg) {// 获得操作,保证后面的读操作在 flag 改变后执行while (!atomic_load_explicit(&flag, memory_order_acquire));// 访问共享内存shared_memory += 20;printf("Thread 2 accessed shared memory: %d\n", shared_memory);return NULL;
}int main() {pthread_t t1, t2;// 创建线程if (pthread_create(&t1, NULL, thread1, NULL) != 0) {perror("pthread_create for thread1");return 1;}if (pthread_create(&t2, NULL, thread2, NULL) != 0) {perror("pthread_create for thread2");return 1;}// 等待线程结束if (pthread_join(t1, NULL) != 0) {perror("pthread_join for thread1");return 1;}if (pthread_join(t2, NULL) != 0) {perror("pthread_join for thread2");return 1;}return 0;
}
输出:
Thread 1 accessed shared memory: 10
Thread 2 accessed shared memory: 30
代码解释
- atomic_store_explicit 使用 memory_order_release 内存顺序,保证在设置 flag
之前,shared_memory 的写操作已完成。 - atomic_load_explicit 使用 memory_order_acquire 内存顺序,保证在 flag 改变后,才会访问
shared_memory。
总结
- 互斥锁和条件变量:适用于复杂的同步场景,能灵活控制线程执行顺序,但实现相对复杂,有一定性能开销。
- 原子操作和内存顺序:实现简单,性能较高,适合对性能要求高且同步逻辑相对简单的场景。