详解linux多线程——互斥锁、条件变量、读写锁、自旋锁、信号量
内容提要
互斥锁用于控制共享资源访问,具有原子性、唯一性和非繁忙等待特点。条件变量与互斥锁同时使用,用于等待特定条件发生。读写锁允许多个线程同时读取,但只允许一个线程写入。自旋锁适用于锁的持有时间较短的情况,阻塞后会忙等待。信号量用于进程或线程间的同步和互斥。
延伸解读
互斥锁的阻塞与非阻塞选择
文章介绍了互斥锁的阻塞加锁(pthread_mutex_lock)和非阻塞尝试加锁(pthread_mutex_trylock、pthread_mutex_timedlock)。阻塞加锁在锁被占用时会让线程休眠,不消耗CPU;非阻塞加锁则立即返回失败或等待超时。实际编码时,若线程不能长时间阻塞,可选用非阻塞版本,但需处理加锁失败后的逻辑,避免忙等待或错误。
条件变量为何必须与互斥锁配合
条件变量用于等待某个条件成立,但条件本身是共享数据,必须用互斥锁保护。文章强调,线程在调用pthread_cond_wait前需先加锁,该函数会原子地释放锁并阻塞,被唤醒后重新获取锁。这样可防止条件检查与等待之间出现竞态。若省略互斥锁,条件判断可能失效,导致逻辑错误。
虚假唤醒与while循环的必要性
文章指出,即使没有线程调用pthread_cond_signal,pthread_cond_wait也可能返回,即虚假唤醒。这可能导致线程在条件不满足时继续执行。因此,等待条件时应使用while循环而非if语句,被唤醒后重新检查条件。虽然while循环略影响效率,但能避免虚假唤醒引发的错误,是更安全的做法。
读写锁与自旋锁的适用场景
读写锁适合读多写少的场景,允许多个线程同时读,但写时独占。自旋锁则适用于锁持有时间极短的情况,阻塞后忙等待而不让出CPU,常用于内核。用户态较少使用自旋锁,因为忙等待会浪费CPU。选择锁类型时,需根据读写比例、临界区长度和上下文切换开销权衡。
Q&A
互斥锁的主要特点是什么?
互斥锁具有原子性、唯一性和非繁忙等待的特点。
条件变量是如何工作的?
条件变量用于等待特定条件的发生,通常与互斥锁一起使用,线程在条件不满足时会被阻塞。
读写锁适合什么场景?
读写锁适合读多写少的场景,允许多个线程同时读取,但只允许一个线程写入。
自旋锁与互斥锁有什么区别?
自旋锁在阻塞后不会让出CPU,而互斥锁会让出CPU,适用于持有时间较短的情况。
信号量的基本操作是什么?
信号量的基本操作包括P操作(减1)和V操作(加1),用于控制对公共资源的访问。
什么是虚假唤醒?
虚假唤醒是指线程在条件不满足时被唤醒,可能导致后续处理出现错误,因此需要在唤醒后检查条件。