面试题答案
一键面试同步机制
- 互斥锁(Mutex):
- 使用场景:用于保护共享资源,如网络缓冲区。当一个线程访问共享资源时,先获取互斥锁,访问完毕后释放。例如,若有一个共享的网络接收缓冲区,在读取或写入数据前,线程需获取互斥锁,防止其他线程同时操作。
- 实现方式:在POSIX线程库(pthread)中,可使用
pthread_mutex_init
初始化互斥锁,pthread_mutex_lock
获取锁,pthread_mutex_unlock
释放锁。
- 信号量(Semaphore):
- 使用场景:可控制同时访问共享资源的线程数量。比如数据库连接池,信号量可限制同时使用连接的线程数,避免过多线程竞争导致资源耗尽。
- 实现方式:使用
sem_init
初始化信号量,sem_wait
获取信号量(相当于将信号量值减1),sem_post
释放信号量(将信号量值加1)。
- 条件变量(Condition Variable):
- 使用场景:用于线程间的同步,当某个条件满足时,通知等待的线程。例如,在生产者 - 消费者模型中,当缓冲区有数据时,生产者线程通过条件变量通知消费者线程。
- 实现方式:结合互斥锁使用,先使用
pthread_cond_init
初始化条件变量,pthread_cond_wait
使线程等待条件变量(同时会自动释放关联的互斥锁,当被唤醒时重新获取互斥锁),pthread_cond_signal
或pthread_cond_broadcast
通知等待的线程。
同步机制优化
- 减少锁的粒度:
- 原理:将大的共享资源分解为多个小的部分,每个部分使用单独的锁。这样不同线程可同时访问不同部分,提高并发性能。例如,对于一个大的网络缓冲区,可按区域划分,每个区域使用一个互斥锁。
- 锁的分层:
- 原理:对于复杂的共享资源访问场景,采用锁的分层策略。如先获取高层锁,再获取低层锁,释放时按相反顺序。避免不同线程获取锁顺序不一致导致死锁。
- 无锁数据结构:
- 原理:在一些场景下,使用无锁数据结构(如无锁队列)。无锁数据结构利用原子操作实现线程安全,避免锁带来的性能开销。但实现较复杂,需仔细处理竞态条件。
避免死锁
- 破坏死锁的四个必要条件:
- 互斥条件:尽量减少对资源的独占访问,若资源支持,可采用共享访问方式。
- 占有并等待条件:要求线程一次性获取所有需要的资源,避免获取部分资源后等待其他资源。
- 不可剥夺条件:允许操作系统或特定机制剥夺线程已占有的资源,以打破死锁。
- 循环等待条件:对资源进行排序,线程按顺序获取资源,避免形成循环等待。
- 死锁检测与恢复:
- 原理:定期检测系统是否存在死锁,若检测到死锁,可选择终止部分线程,释放资源,恢复系统正常运行。例如,通过记录线程获取锁的顺序和持有时间等信息,分析是否存在死锁。
多线程网络编程常见陷阱及解决方案
- 竞态条件(Race Condition):
- 问题描述:多个线程同时访问和修改共享资源,导致结果不可预测。
- 解决方案:使用同步机制(如上述的互斥锁、信号量等)保护共享资源。
- 线程安全问题:
- 问题描述:部分函数或数据结构在多线程环境下不是线程安全的,可能导致程序崩溃或数据错误。
- 解决方案:使用线程安全的函数库或对非线程安全的函数进行封装,加入同步机制。
- 线程泄漏(Thread Leak):
- 问题描述:线程创建后未正确释放,导致资源浪费。
- 解决方案:确保线程正常结束,并在适当位置调用
pthread_join
等待线程结束,释放资源。若线程需要分离,使用pthread_detach
正确分离。
- 上下文切换开销:
- 问题描述:过多线程导致频繁上下文切换,降低系统性能。
- 解决方案:合理控制线程数量,根据系统资源和任务类型确定最优线程数。可使用线程池技术,复用线程,减少线程创建和销毁开销。