我试图更好地理解c ++线程。我实现了一个小例子,我想要四个线程来处理std :: vector中的索引区域。
当然我遇到了死锁,并开始使用互斥锁和锁。据我了解,一般的假设是所有变量都由线程共享,除非另有明确说明(thread_local)。如果一个线程改变了任何全局数据,那么首先锁定资源是明智的,对数据进行处理以避免数据竞争,然后再次解锁数据,以便其他线程可以使用它。
在我的例子中,std :: cout上的锁工作正常,线程创建正常,函数被称为正常,但程序仍然挂起,即使我在操作数据之前和之后实现了data_lock。 如果我注释掉数据操作并显示消息,它也可以正常工作。输出与运行不同,所以我不发布它。
我的感觉是我错过了一些我不知道的c ++线程的概念(之前我曾与MPI合作过)。
我的问题:
编译器指令:
g ++ -std = c ++ 1y -O0 -g3 -Wall -c -fmessage-length = 0 -pthread -MMD -MP -MF" src / main.d" -MT" SRC / main.d" -o" src / main.o" " ../的src / main.cpp中"
码
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
using namespace std;
std::mutex data_lock;
std::mutex cout_lock;
void output(std::string message){
cout_lock.lock();
cout << message << endl;
cout_lock.unlock();
}
void work(std::vector<double>& data, const int s_ind, const int e_ind) {
thread_local int i = 0;
for (i = s_ind; i <= e_ind; i++) {
data_lock.lock();
data[i] = 1.0;
data_lock.unlock();
//msg("work");
}
}
int main() {
const int size = 1000;
const int cpus = 4;
const int chunksize = size / cpus;
//create Data vector
std::vector<double> dat { (size) };
//thread vector
std::vector<std::thread> threads;
//create and start threads with proper ranges (ranges tested)
for (int cpu = 0; cpu < cpus; cpu++) {
threads.push_back(std::thread(work, ref(dat), (cpu * chunksize),(((cpu + 1) * chunksize) - 1)));
output("thread created");
}
//delete threads
for (int cpu = 0; cpu < cpus; cpu++) {
threads[cpu].join();
output("thread joined");
}
return 0;
}
答案 0 :(得分:0)
我知道有人建议在ctor中使用{}
,它可以消除隐式函数声明的问题。但如果类具有std::initializer_list<T>
作为参数的构造函数重载并且std::vector具有问题,那么您就会遇到问题。所以这一行:
std::vector<double> dat { (size) };
创建一个元素size
的向量。
由于您声明遇到死锁并且必须使用互斥锁,这几乎是不可能的。最有可能你的程序挂起,但这不算死锁。死锁是一种情况,当你的线程阻塞彼此试图以错误的顺序(或类似的情况)锁定互斥锁。
注意:这不是修复,但你应该使用RAII进行互斥锁定,标准库为此提供工具:std::lock_guard,std::unique_lock或std::scoped_lock如果你有c ++ 17。理由,为什么在这种情况下应该使用RAII(以及许多其他),here
RAII保证资源可用于任何功能 可以访问该对象(资源可用性是一个类不变量, 消除冗余运行时测试)。它也保证了所有 资源在其控制对象的生命周期中释放 以收购的相反顺序结束。同样,如果资源 获取失败(构造函数以异常退出),全部 每个完全构建的成员和基地获得的资源 子对象以初始化的相反顺序释放。这个 利用核心语言功能(对象生命周期,范围退出, 初始化和堆栈展开的顺序)以消除资源 泄漏并保证异常安全。这种技术的另一个名称 在基本用例之后是范围限制资源管理(SBRM) 其中RAII对象的生命周期因范围退出而终止。