使用POSIX线程库(pthread)实现线程同步防止数据竞争的方法
- 互斥锁(Mutex)
- 原理:互斥锁是一种二元信号量,它只有两种状态:锁定(locked)和解锁(unlocked)。当一个线程获取了互斥锁(将其锁定),其他线程在试图获取该互斥锁时就会被阻塞,直到该互斥锁被解锁。
- 代码示例:
#include <pthread.h>
#include <stdio.h>
// 共享数据
int shared_variable = 0;
// 互斥锁
pthread_mutex_t mutex;
void* increment(void* arg) {
// 加锁
pthread_mutex_lock(&mutex);
shared_variable++;
// 解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread1, thread2;
// 初始化互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建线程
pthread_create(&thread1, NULL, increment, NULL);
pthread_create(&thread2, NULL, increment, NULL);
// 等待线程结束
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
printf("Final value of shared_variable: %d\n", shared_variable);
return 0;
}
- 读写锁(Read - Write Lock)
- 原理:读写锁允许多个线程同时进行读操作,但只允许一个线程进行写操作。当有线程进行写操作时,其他读线程和写线程都将被阻塞。这样可以提高读操作的并发性能,因为读操作不会修改数据,所以多个读操作可以同时进行而不会引发数据竞争。
- 代码示例:
#include <pthread.h>
#include <stdio.h>
// 共享数据
int shared_variable = 0;
// 读写锁
pthread_rwlock_t rwlock;
void* reader(void* arg) {
// 读锁
pthread_rwlock_rdlock(&rwlock);
printf("Reader read: %d\n", shared_variable);
// 解锁
pthread_rwlock_unlock(&rwlock);
return NULL;
}
void* writer(void* arg) {
// 写锁
pthread_rwlock_wrlock(&rwlock);
shared_variable++;
// 解锁
pthread_rwlock_unlock(&rwlock);
return NULL;
}
int main() {
pthread_t reader1, reader2, writer1;
// 初始化读写锁
pthread_rwlock_init(&rwlock, NULL);
// 创建线程
pthread_create(&reader1, NULL, reader, NULL);
pthread_create(&reader2, NULL, reader, NULL);
pthread_create(&writer1, NULL, writer, NULL);
// 等待线程结束
pthread_join(reader1, NULL);
pthread_join(reader2, NULL);
pthread_join(writer1, NULL);
// 销毁读写锁
pthread_rwlock_destroy(&rwlock);
return 0;
}
- 条件变量(Condition Variable)
- 原理:条件变量用于线程间的同步,它允许线程等待某个条件满足。通常与互斥锁一起使用,一个线程获取互斥锁后,检查条件是否满足,如果不满足则在条件变量上等待,同时释放互斥锁。当另一个线程改变了条件并通知条件变量时,等待的线程被唤醒,重新获取互斥锁并检查条件。
- 代码示例:
#include <pthread.h>
#include <stdio.h>
// 共享数据
int shared_variable = 0;
// 互斥锁
pthread_mutex_t mutex;
// 条件变量
pthread_cond_t cond;
void* waiter(void* arg) {
pthread_mutex_lock(&mutex);
while (shared_variable == 0) {
// 等待条件变量,会释放互斥锁
pthread_cond_wait(&cond, &mutex);
}
printf("Waiter woke up, shared_variable: %d\n", shared_variable);
pthread_mutex_unlock(&mutex);
return NULL;
}
void* signaler(void* arg) {
pthread_mutex_lock(&mutex);
shared_variable = 1;
// 通知条件变量
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t waiter_thread, signaler_thread;
// 初始化互斥锁和条件变量
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
// 创建线程
pthread_create(&waiter_thread, NULL, waiter, NULL);
pthread_create(&signaler_thread, NULL, signaler, NULL);
// 等待线程结束
pthread_join(waiter_thread, NULL);
pthread_join(signaler_thread, NULL);
// 销毁互斥锁和条件变量
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
复杂数据结构和高并发场景下的挑战及解决方法
- 死锁
- 挑战描述:死锁是指两个或多个线程相互等待对方释放资源,从而导致所有线程都无法继续执行的情况。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1。
- 解决方法:
- 避免嵌套锁:尽量减少锁的嵌套使用,如果必须使用,确保所有线程以相同的顺序获取锁。
- 资源分配图算法:使用资源分配图算法(如银行家算法)来检测和预防死锁,但这种方法在实际应用中复杂度较高,通常适用于资源数量有限且可预测的场景。
- 超时机制:为获取锁设置超时时间,如果在规定时间内未能获取到锁,则放弃并释放已获取的锁,然后重新尝试获取锁。
- 性能问题
- 挑战描述:在高并发场景下,频繁的加锁和解锁操作会带来性能开销,尤其是当锁的粒度较大时,会导致线程竞争激烈,降低系统的并发性能。
- 解决方法:
- 减小锁的粒度:将大锁分解为多个小锁,使得不同的线程可以同时访问不同部分的共享数据,减少线程竞争。例如,对于一个包含多个元素的数组,可以为每个元素或每组元素设置单独的锁。
- 使用无锁数据结构:对于一些简单的数据结构,如栈、队列等,可以使用无锁数据结构来提高并发性能。无锁数据结构通过使用原子操作来避免锁的使用,从而减少线程竞争。
- 读写分离:如果共享数据的读操作远多于写操作,可以使用读写锁(如前面提到的)来提高读操作的并发性能。
- 复杂数据结构同步
- 挑战描述:当共享数据是复杂数据结构(如链表、树等)时,同步操作变得更加复杂。例如,在链表的插入或删除操作中,需要保证操作的原子性,同时要考虑锁的粒度和线程安全。
- 解决方法:
- 设计线程安全的数据结构:在设计复杂数据结构时,考虑如何使其线程安全。例如,对于链表,可以使用细粒度锁,每个节点都有自己的锁,在进行插入或删除操作时,只需要获取相关节点的锁。
- 使用事务机制:借鉴数据库事务的思想,将对复杂数据结构的一系列操作作为一个原子事务,要么全部成功,要么全部失败。在事务开始时获取所有需要的锁,在事务结束时释放锁。不过,实现事务机制需要一定的设计和编程技巧。