在Linux环境下进行多线程开发时,多个线程并发访问同一块共享内存是常见场景,若没有合理的同步控制,就会出现竞态条件,导致内存中的数据被错误修改,程序运行结果不符合预期。这类问题的本质是多个线程对共享资源的访问没有顺序约束,执行时序的不确定性会引发数据不一致。

多线程并发访问内存的常见问题
当多个线程同时读写同一块内存区域时,最常见的两类问题是数据覆盖和中间状态暴露。比如两个线程同时对一个全局计数器做加1操作,正常情况下两次加1后计数器应该增加2,但若没有同步,可能出现两个线程同时读取到旧值,各自加1后写回,最终计数器只增加了1。
这类问题在调试时往往很难复现,因为线程调度顺序是不确定的,只有在特定时序下才会触发异常,所以需要在开发阶段就通过同步机制提前规避。
核心解决方案:同步机制的使用
1. 互斥锁(Mutex)
互斥锁是最常用的同步工具,它的核心逻辑是同一时间只允许一个线程持有锁,访问共享内存,其他线程需要等待锁释放后才能获取锁并访问资源,从根源上避免多个线程同时操作共享内存。
Linux下使用pthread库的互斥锁需要包含<pthread.h>头文件,基本使用流程如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 定义共享内存变量
int shared_num = 0;
// 定义互斥锁
pthread_mutex_t shared_mutex;
// 线程执行函数,对共享变量做加1操作
void* thread_func(void* arg) {
for (int i = 0; i < 10000; i++) {
// 加锁,若锁已被其他线程持有则阻塞等待
pthread_mutex_lock(&shared_mutex);
// 访问共享内存
shared_num++;
// 解锁,释放锁给其他线程使用
pthread_mutex_unlock(&shared_mutex);
}
return NULL;
}
int main() {
pthread_t t1, t2;
// 初始化互斥锁,使用默认属性
pthread_mutex_init(&shared_mutex, NULL);
// 创建两个线程
pthread_create(&t1, NULL, thread_func, NULL);
pthread_create(&t2, NULL, thread_func, NULL);
// 等待两个线程执行完成
pthread_join(t1, NULL);
pthread_join(t2, NULL);
// 输出共享变量结果,预期为20000
printf("shared_num final value: %dn", shared_num);
// 销毁互斥锁
pthread_mutex_destroy(&shared_mutex);
return 0;
}
上面的代码中,两个线程对shared_num做10000次加1操作,通过互斥锁保证每次加1操作是原子的,最终输出结果会是20000,不会出现数据丢失的情况。
2. 读写锁(RWLock)
如果共享内存的访问场景是读多写少,使用互斥锁会让所有读操作也串行执行,降低效率。这时候可以使用读写锁,它允许多个线程同时读共享内存,但写操作时需要独占锁,同一时间只能有一个线程写,且写的时候不能有线程读。
读写锁的基本使用示例如下:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 共享内存数据
char shared_data[128] = "init data";
// 定义读写锁
pthread_rwlock_t rwlock;
// 读线程函数
void* read_thread(void* arg) {
for (int i = 0; i < 5; i++) {
// 加读锁
pthread_rwlock_rdlock(&rwlock);
printf("read thread %ld, data: %sn", (long)arg, shared_data);
// 解读锁
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
// 写线程函数
void* write_thread(void* arg) {
for (int i = 0; i < 3; i++) {
// 加写锁
pthread_rwlock_wrlock(&rwlock);
snprintf(shared_data, sizeof(shared_data), "update data %d", i);
printf("write thread update data to: %sn", shared_data);
// 解写锁
pthread_rwlock_unlock(&rwlock);
sleep(2);
}
return NULL;
}
int main() {
pthread_t r1, r2, w1;
// 初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
// 创建两个读线程和一个写线程
pthread_create(&r1, NULL, read_thread, (void*)1);
pthread_create(&r2, NULL, read_thread, (void*)2);
pthread_create(&w1, NULL, write_thread, NULL);
// 等待所有线程结束
pthread_join(r1, NULL);
pthread_join(r2, NULL);
pthread_join(w1, NULL);
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
3. 条件变量(Condition Variable)
条件变量通常和互斥锁配合使用,用于解决线程间的同步问题,比如一个线程需要等待另一个线程修改共享内存到某个状态后再继续执行。典型场景是生产者消费者模型,生产者线程往共享队列写数据,消费者线程等待队列有数据后再读取。
条件变量的使用示例:
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
// 共享队列,最多存5个元素
int queue[5];
int queue_size = 0;
// 互斥锁和条件变量
pthread_mutex_t queue_mutex;
pthread_cond_t queue_not_empty;
pthread_cond_t queue_not_full;
// 生产者线程函数
void* producer(void* arg) {
int num = 0;
while (1) {
pthread_mutex_lock(&queue_mutex);
// 队列满了就等待条件变量
while (queue_size == 5) {
pthread_cond_wait(&queue_not_full, &queue_mutex);
}
// 往队列写数据
queue[queue_size] = num++;
queue_size++;
printf("producer add num: %d, queue size: %dn", queue[queue_size-1], queue_size);
// 通知消费者队列不为空
pthread_cond_signal(&queue_not_empty);
pthread_mutex_unlock(&queue_mutex);
sleep(1);
}
return NULL;
}
// 消费者线程函数
void* consumer(void* arg) {
while (1) {
pthread_mutex_lock(&queue_mutex);
// 队列空了就等待条件变量
while (queue_size == 0) {
pthread_cond_wait(&queue_not_empty, &queue_mutex);
}
// 从队列读数据
int val = queue[0];
for (int i = 0; i < queue_size - 1; i++) {
queue[i] = queue[i+1];
}
queue_size--;
printf("consumer get num: %d, queue size: %dn", val, queue_size);
// 通知生产者队列不满
pthread_cond_signal(&queue_not_full);
pthread_mutex_unlock(&queue_mutex);
sleep(2);
}
return NULL;
}
int main() {
pthread_t p, c;
// 初始化互斥锁和条件变量
pthread_mutex_init(&queue_mutex, NULL);
pthread_cond_init(&queue_not_empty, NULL);
pthread_cond_init(&queue_not_full, NULL);
// 创建生产者和消费者线程
pthread_create(&p, NULL, producer, NULL);
pthread_create(&c, NULL, consumer, NULL);
// 等待线程(实际场景可加退出逻辑)
pthread_join(p, NULL);
pthread_join(c, NULL);
// 销毁资源
pthread_mutex_destroy(&queue_mutex);
pthread_cond_destroy(&queue_not_empty);
pthread_cond_destroy(&queue_not_full);
return 0;
}
不同场景的方案选择
如果共享内存的访问是简单的读写操作,且读写频率差不多,优先选择互斥锁,实现简单且可靠性高。如果是读多写少的场景,读写锁能提升整体性能。如果线程之间需要基于共享内存的状态做协作,比如等待某个条件成立再执行,就需要配合条件变量使用。
另外需要注意,所有同步工具都需要正确初始化和销毁,避免资源泄漏。加锁的范围要尽量小,只包裹操作共享内存的代码段,减少锁持有的时间,避免影响程序并发性能。