1. 线程安全问题场景
- 数据竞争:当多个线程同时访问和修改共享数据时,可能会导致数据不一致。例如,在并行排序中,多个线程可能同时尝试修改数组中的元素。
- 死锁:当两个或多个线程相互等待对方释放资源时,会发生死锁。例如,线程A持有锁1并等待锁2,而线程B持有锁2并等待锁1。
2. 解决方法
- 锁机制:
- 互斥锁(Mutex):最常用的锁类型。在访问共享数据前,线程必须先获取互斥锁,访问结束后释放。例如:
#include <iostream>
#include <vector>
#include <algorithm>
#include <mutex>
#include <thread>
std::mutex mtx;
void parallel_sort_section(std::vector<int>& data, int start, int end) {
std::sort(data.begin() + start, data.begin() + end);
}
void parallel_sort(std::vector<int>& data, int num_threads) {
int size = data.size();
int chunk_size = size / num_threads;
std::vector<std::thread> threads;
for (int i = 0; i < num_threads; ++i) {
int start = i * chunk_size;
int end = (i == num_threads - 1)? size : (i + 1) * chunk_size;
threads.emplace_back(parallel_sort_section, std::ref(data), start, end);
}
for (auto& th : threads) {
if (th.joinable()) {
th.join();
}
}
// 合并各部分
for (int i = 1; i < num_threads; ++i) {
std::vector<int> temp;
int start1 = 0, end1 = i * chunk_size;
int start2 = i * chunk_size, end2 = (i == num_threads - 1)? size : (i + 1) * chunk_size;
std::merge(data.begin() + start1, data.begin() + end1, data.begin() + start2, data.begin() + end2, std::back_inserter(temp));
mtx.lock();
std::copy(temp.begin(), temp.end(), data.begin() + start1);
mtx.unlock();
}
}
- 无锁数据结构:使用无锁数据结构(如无锁队列、无锁哈希表)可以避免锁带来的开销。在排序场景中,虽然直接使用无锁数据结构较难,但可以在辅助数据结构(如任务队列)中使用。
- 其他同步策略:
- 条件变量(Condition Variable):用于线程间的同步通信。例如,当某个线程完成排序任务后,通过条件变量通知其他线程进行合并操作。
3. 代码工作原理
- parallel_sort_section函数:负责对数组的一个子区间进行排序,这里使用标准库的
std::sort
。
- parallel_sort函数:
- 首先将数组划分为
num_threads
个区间,每个区间由一个线程负责排序。
- 创建线程并启动,每个线程执行
parallel_sort_section
函数。
- 等待所有线程完成排序。
- 然后通过
std::merge
函数将各个子区间合并,在合并过程中使用互斥锁mtx
来保证数据的线程安全,防止在合并时其他线程对数据进行修改。