并发编程实战笔记
并行累加求和
template<class Iterator, class T>//Iterator 迭代器类型,T 内置数据类型
accumulate_block(Iterator first, Iterator last, T& result) {
result = accumulate(first, last, result);
}
template<class Iterator, class T>
T parallel_accumulate(Iterator first, Iterator last, T init) {
const unsigned long length = std::distance(first, last);//distance 求迭代器间距离
if (!length) return init;
const unsigned long min_per_thread = 25;
const unsigned long max_threads = (length + min_per_thread - 1) / min_per_thread;
const unsigned long hardware_threads = thread::hardware_concurrency();//cpu支持线程并发数
const unsigned long num_threads = min(hardware_threads != 0 ? hardware_threads : 2, max_threads);
const unsigned long block_size = length / num_threads;
vector<T> results(num_threads);
vector<thread> threads(num_threads - 1);//加上当前线程 凑够num_threads
Iterator block_start = first;
for (unsigned long i = 0; i < (num_threads - 1); i++) {
Iterator block_end = block_start;
advance(block_end, block_size);
threads[i] = thread(accumulate_block<Iterator, T>, block_start, block_end, ref(results[i]));
block_start = block_end;
}
accumulate_block(block_start, last, results[num_threads - 1]);
for_each(threads.begin(),threads.end(),mem_fn(&thread::join));//阻塞等待线程结束
return std::accumulate(results.begin(), results.end(), init);//累加个线程结果并返回
}
int main() {
vector<int> vi;
for (int i = 0; i < 48516; i++) {
vi.push_back(10);
}
int sum = parallel_accumulate(vi.begin(), vi.end(), 5);//accumulate形参:首指针,尾指针,初始值
cout << "sum = " << sum << endl;
return EXIT_SUCCESS;
}
数据共享
三种方式实现数共享:
- 有锁保护,对数据结构采用某种保护机制,确保只有进行修改的线程才能看到不变量被破坏时的中间状态。
- 无锁保护,对数据结构和不变量的设计进行修改,修改完的结构必须能完成一系列不可分割的变化,也就是保证每个不变量保持稳定的状态,这就是所谓的无锁编程。
- 事务形式,所需的一些数据和读取都存储在事务日志中,然后将之前的操作合为一步,再进行提交。
避免死锁的建议
- 使用固定顺序获取锁:
在每个线程上,让两个互斥量总以相同的顺序上锁。 - 同时获取所有资源,
std::lock
要么锁住所有资源,要么一个都不锁。 - 避免嵌套锁:一个线程已获得一个锁时,别再去获取第二个。
- 避免在持有锁时调用用户提供的代码:
在持有锁的情况下调用用户代码,而用户代码中可能有获取其他锁的操作。 - 使用锁的层次结构:
当代码试图对一个互斥量上锁,在该层锁已被底层持有时,上锁是不允许的。
保护很少更新的数据结构
这里需要另一种不同的互斥量,这种互斥量常被称为“读者-作者锁”,因为其允许两种不同的使用方式:
一个“作者”
线程独占访问和共享访问,让多个“读者”
线程并发访问。
对于更新操作,可以使用 std::lock_guard<boost::shared_mutex>
和 std::unique_lock<boost::shared_mutex>
上锁。作为 std::mutex
的替代方案,与 std::mutex
所做的一样,这就能保证更新线程的独占访问。因为其他线程不需要去修改数据结构,所以其可以使用 boost::shared_lock<boost::shared_mutex>
获取访问权。
update_or_add_entry()
函数调用时,独占锁会阻止其他线程对数据结构进行修改,并且阻止线程调用find_entry()
。
class dns_cache
{
std::map<std::string,dns_entry> entries;
boost::shared_mutex entry_mutex;
public:
dns_entry find_entry(std::string const& domain)
{
boost::shared_lock<boost::shared_mutex> lk(entry_mutex);
std::map<std::string,dns_entry>::const_iterator const it=
entries.find(domain);
return (it==entries.end())?dns_entry():it->second;
}
void update_or_add_entry(std::string const& domain,
dns_entry const& dns_details)
{
std::lock_guard<boost::shared_mutex> lk(entry_mutex);
entries[domain]=dns_details;
}
};