面试题答案
一键面试使用信号量解决线程安全问题
- 初始化信号量:
- 初始化一个信号量用于控制文件资源的访问,假设命名为
fileSemaphore
,初始值设为1,表示同一时间只允许一个线程访问文件。
- 初始化一个信号量用于控制文件资源的访问,假设命名为
- 主线程写入操作:
- 在主线程执行写入文件操作前,调用
sem_wait(fileSemaphore)
,获取信号量。这会导致如果此时有其他线程正在访问文件(信号量值为0),主线程会阻塞等待。 - 主线程成功获取信号量后(信号量值减为0),执行文件写入操作。
- 写入操作完成后,调用
sem_post(fileSemaphore)
,释放信号量,将信号量值加1,允许其他线程获取信号量访问文件。
- 在主线程执行写入文件操作前,调用
- 子线程读取操作:
- 每个子线程在执行读取文件操作前,同样调用
sem_wait(fileSemaphore)
获取信号量。 - 成功获取信号量后执行文件读取操作。
- 读取完成后,调用
sem_post(fileSemaphore)
释放信号量。
- 每个子线程在执行读取文件操作前,同样调用
使用互斥锁解决线程安全问题
- 初始化互斥锁:
- 初始化一个互斥锁
fileMutex
,用于保护文件资源的临界区。
- 初始化一个互斥锁
- 主线程写入操作:
- 在主线程执行写入文件操作前,调用
pthread_mutex_lock(&fileMutex)
锁定互斥锁。如果此时互斥锁已被其他线程锁定,主线程会阻塞等待。 - 主线程成功锁定互斥锁后,进入临界区,执行文件写入操作。
- 写入操作完成后,调用
pthread_mutex_unlock(&fileMutex)
解锁互斥锁,允许其他线程进入临界区。
- 在主线程执行写入文件操作前,调用
- 子线程读取操作:
- 每个子线程在执行读取文件操作前,调用
pthread_mutex_lock(&fileMutex)
锁定互斥锁。 - 成功锁定后执行文件读取操作。
- 读取完成后,调用
pthread_mutex_unlock(&fileMutex)
解锁互斥锁。
- 每个子线程在执行读取文件操作前,调用
两种方案优缺点分析
- 信号量优点:
- 灵活性高:信号量的值可以大于1,在某些场景下可以允许多个线程同时访问文件资源,例如可以设置信号量初始值为3,允许3个线程同时读取文件,提高读取的并发性能。
- 资源计数:信号量本身可以作为一种资源计数机制,通过观察信号量的值,可以了解当前文件资源的使用情况。
- 信号量缺点:
- 复杂度高:相比互斥锁,信号量的使用和理解更复杂,尤其是在处理信号量值的增减逻辑时,容易出现逻辑错误。
- 性能开销:信号量操作通常涉及系统调用,性能开销相对较大。
- 互斥锁优点:
- 简单直观:互斥锁的逻辑简单,只有锁定和解锁两种状态,易于理解和使用,编程实现相对简单。
- 性能较好:互斥锁的实现通常基于内核的原子操作,性能开销相对较小。
- 互斥锁缺点:
- 并发度低:互斥锁同一时间只允许一个线程进入临界区,对于多线程读取场景,会限制读取的并发性能,可能导致读取效率不高。
- 功能单一:互斥锁主要用于保护临界区,不像信号量那样具有资源计数等额外功能。