在写程序时,如果多个线程同时操作同一个数据,就像几个人同时往一个记事本上写东西,很容易乱套。比如一个线程正在修改余额,另一个线程也来读取或写入,结果可能就是钱算错了。这时候就需要线程同步机制,它像是给多线程世界定下的“交通规则”,让它们有序通行,避免撞车。
为什么需要线程同步?
想象你在超市抢购最后一瓶酱油,你和另一个人同时把酱油放进购物车,系统却显示库存还有1,结果结账时才发现其实已经卖光了。这就是典型的“竞态条件”——多个线程同时访问共享资源,导致结果不可预测。线程同步就是为了解决这类问题。
常见的同步方式
最常用的同步工具是“互斥锁”(Mutex)。它像是一把钥匙,只有拿到钥匙的线程才能进入临界区操作数据,其他线程只能排队等待。用代码来看更清楚:
#include <pthread.h>
#include <stdio.h>
int balance = 100;
pthread_mutex_t lock;
void* withdraw(void* arg) {
pthread_mutex_lock(&lock);
balance -= 10;
printf("余额: %d\n", balance);
pthread_mutex_unlock(&lock);
return NULL;
}
这里,每次只有一个线程能执行 balance -= 10; 这行代码,避免了并发修改带来的错误。
信号量:控制并发数量
有时候我们不只想让一个线程通过,而是限制最多三个线程同时工作,比如数据库连接池。这时候可以用信号量(Semaphore)。它像停车场的空位指示牌,每进一辆车就减一,满了就等。
条件变量:等个信儿再动
有些场景下,线程需要等某个条件成立才继续,比如消费者线程要等队列里有数据才取。条件变量配合互斥锁使用,可以让线程“睡着”,直到别的线程通知它“可以干活了”。
比如银行柜台叫号,你拿了号坐在那儿不动,等到叫你名字才起身办理业务。代码中就是这样:
pthread_mutex_lock(&mutex);
while (queue_empty()) {
pthread_cond_wait(&cond, &mutex);
}
// 处理任务
pthread_mutex_unlock(&mutex);
读写锁:读多写少的优化
如果数据大部分时间被读取,只有偶尔修改,用互斥锁会浪费性能——本来可以多人同时读,却被强制排队。读写锁允许同时多个读操作,但写操作必须独占。就像图书馆里,大家都能同时看书(读),但修改书内容时(写),得清场。
原子操作:轻量级同步
对于简单的变量增减,比如计数器,可以用原子操作。它由硬件支持,保证一条指令完成,不会被打断。比加锁更高效,适合简单场景。
现代编程语言大多封装了这些机制。Java 有 synchronized 和 ReentrantLock,Python 的 threading 模块提供 Lock、Condition 等工具,Go 用 channel 和 sync 包处理并发协调。