当使用std::thread时,主线程中的循环被卡住 我有一个简单的C++代码来测试和理解线程。代码具有主线程+次线程。 次线程更新主线程循环所依赖的变量的值。当我在主循环中添加一个print语句时,程序将成功完成,但当我删除这个print语句时,它将进入一个无限循环。 这是我正在使用的代码,我所指的print语句是print语句2 #include <mpi.h> #include <iostream> #include <fstream> #include <thread> #include <mutex> std::mutex mu; int num; using namespace std; void WorkerFunction() { bool work = true; while(work) { mu.lock(); num --; mu.unlock(); if(num == 1) work = false; } } int main(int argc, char **argv) { bool work = true; num = 10; int numRanks, myRank, provided; MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &provided); MPI_Comm_size(MPI_COMM_WORLD, &numRanks); MPI_Comm_rank(MPI_COMM_WORLD, &myRank); std::thread workThread (WorkerFunction); //print statement 1 cerr<<"Rank "<<myRank<<" Started workThread \n"; int mult = 0; while(work) { mult += mult * num; //print statement 2 if(myRank == 0) cerr<<"num = "<<num<<"\n"; if(num == 1) work = false; } if(work == false) workThread.join(); //print statement 3 cerr<<"Rank "<<myRank<<" Done with both threads \n"; MPI_Finalize(); }; mpirun -np 4 ./Testing Rank 0 Started workThread num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 Rank 1 Started workThread Rank 0 Done with both threads Rank 1 Done with both threads Rank 2 Started workThread Rank 3 Started workThread Rank 2 Done with both threads Rank 3 Done with both threads
如果我注释掉那个print语句,它就会进入一个infinte循环,这就是我得到的输出当使用std::thread时,主线程中的循环被卡住 我有一个简单的C++代码来测试和理解线程。代码具有主线程+次线程。 次线程更新主线程循环所依赖的变量的值。当我在主循环中添加一个print语句时,程序将成功完成,但当我删除这个print语句时,它将进入一个无限循环。 这是我正在使用的代码,我所指的print语句是print语句2 #include <mpi.h> #include <iostream> #include <fstream> #include <thread> #include <mutex> std::mutex mu; int num; using namespace std; void WorkerFunction() { bool work = true; while(work) { mu.lock(); num --; mu.unlock(); if(num == 1) work = false; } } int main(int argc, char **argv) { bool work = true; num = 10; int numRanks, myRank, provided; MPI_Init_thread(&argc, &argv, MPI_THREAD_FUNNELED, &provided); MPI_Comm_size(MPI_COMM_WORLD, &numRanks); MPI_Comm_rank(MPI_COMM_WORLD, &myRank); std::thread workThread (WorkerFunction); //print statement 1 cerr<<"Rank "<<myRank<<" Started workThread \n"; int mult = 0; while(work) { mult += mult * num; //print statement 2 if(myRank == 0) cerr<<"num = "<<num<<"\n"; if(num == 1) work = false; } if(work == false) workThread.join(); //print statement 3 cerr<<"Rank "<<myRank<<" Done with both threads \n"; MPI_Finalize(); }; mpirun -np 4 ./Testing Rank 0 Started workThread num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 num = 10 Rank 1 Started workThread Rank 0 Done with both threads Rank 1 Done with both threads Rank 2 Started workThread Rank 3 Started workThread Rank 2 Done with both threads Rank 3 Done with both threads,c++,multithreading,mpi,infinite-loop,stdthread,C++,Multithreading,Mpi,Infinite Loop,Stdthread,如果我注释掉那个print语句,它就会进入一个infinte循环,这就是我得到的输出 mpirun -np 4 ./Testing Rank 0 Started workThread Rank 0 Done with both threads Rank 1 Started workThread Rank 2 Started workThread Rank 3 Started workThread Rank 2 Done with both threads Rank 3 Done wi
mpirun -np 4 ./Testing
Rank 0 Started workThread
Rank 0 Done with both threads
Rank 1 Started workThread
Rank 2 Started workThread
Rank 3 Started workThread
Rank 2 Done with both threads
Rank 3 Done with both threads
我不确定我做错了什么,非常感谢您的帮助。关于MPI,我没有任何经验。我几十年前就用过它了,我相信这个事实毫无价值。然而,OP声称
我有一个简单的C++代码来测试和理解线程。 考虑到使用MPI进行多处理以及使用std::thread进行多线程处理本身都是复杂的主题,我将首先将这些主题分开,并在获得每一个主题的一些经验后尝试将它们放在一起
因此,我详细阐述了我认为能够实现的多线程处理 第一个示例是OPs代码的修订版,删除了所有对MPI的引用: 注: 当多线程运行时,变量num被共享,并且变量num在至少一个线程中被修改,每个访问都应该被放入一对互斥锁和解锁中 关键部分应始终尽可能短。一次只能有一个线程通过临界段。因此,它引入了重新序列化,这会消耗并发所需的速度。我在每个线程中引入了一个局部变量num_u,以复制共享变量的当前值,并在各个线程的临界部分之后使用它* 为了更好地说明,我在两个线程中都添加了一个sleep_。没有,我有 总数:10 数目:1 两个线程都完成了。 我觉得有点无聊 输出跳过num==9并打印num==2两次。这在其他跑步中可能会有不同的表现。原因是线程按照定义异步工作。两个线程中相等的100毫秒延迟不是可靠的同步。如果没有任何东西(例如锁定的互斥锁)阻止此操作,则操作系统负责唤醒线程。它可以随时挂起线程 关于mtxNum.lock/mtxNum.unlock:假设临界部分包含比简单的-num更复杂的内容;这可能引发异常。如果引发异常,将跳过mtxNum.unlock,并生成一个阻止任何线程继续的进程 为此,std库提供了一个非常好用的工具: 使用std::lock_-guard的诀窍是,在任何情况下,析构函数都会解锁互斥锁,即使在关键部分内抛出异常也是如此 可能是,我有点偏执,但让我恼火的是,对共享变量的非保护访问可能是偶然发生的,而在任何调试会话或任何编译器诊断中都不会被注意到。**因此,将共享变量隐藏到一个类中可能是值得的,在该类中,只有锁定它才能访问。为此,我在示例中介绍了Shared:#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
template <typename T>
class Shared {
public:
struct Lock {
Shared &shared;
std::lock_guard<std::mutex> lock;
Lock(Shared &shared): shared(shared), lock(shared._mtx) { }
~Lock() = default;
Lock(const Lock&) = delete;
Lock& operator=(const Lock&) = delete;
const T& get() const { return shared._value; }
T& get() { return shared._value; }
};
private:
std::mutex _mtx;
T _value;
public:
Shared() = default;
explicit Shared(T &&value): _value(std::move(value)) { }
~Shared() = default;
Shared(const Shared&) = delete;
Shared& operator=(const Shared&) = delete;
};
typedef Shared<int> SharedInt;
SharedInt shNum(10);
const std::chrono::milliseconds delay(100);
void WorkerFunction()
{
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
int num_;
{ SharedInt::Lock lock(shNum);
num_ = --lock.get();
}
work = num_ != 1;
}
}
int main()
{
std::thread workThread(&WorkerFunction);
int mult = 0;
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
int num_;
{ const SharedInt::Lock lock(shNum);
num_ = lock.get();
}
std::cout << "num: " << num_ << '\n';
mult += mult * num_;
work = num_ != 1;
}
if (workThread.joinable()) workThread.join();
std::cout << "Both threads done.\n";
}
int&num的生存期刚好在SharedInt::Lock lockshNum;的生存期之前结束
当然,我们可以得到一个指向Num的指针,在范围之外使用它,但我会认为这是破坏。< /P> 另一件事,我想提及的是:
原子库为细粒度原子操作提供组件,允许无锁并发编程。对于涉及同一对象的任何其他原子操作,每个原子操作都是不可分割的 虽然互斥锁可能是操作系统内核函数的主题,但可以利用CPU特性进行原子访问,而无需进入内核。这可能会提高速度,并减少操作系统资源的使用 如果resp没有H/W支持,则更好。键入available可根据以下说明返回到基于互斥锁或其他锁定操作的实现: 除std::atomic_标志外的所有原子类型都可以使用互斥锁或其他锁定操作来实现,而不是使用无锁原子CPU指令。原子类型有时也允许是无锁的,例如,如果给定体系结构上只有对齐的内存访问自然是原子的,则相同类型的未对齐对象必须使用锁 使用std::atomic修改的样本: *我在阅读中沉思了一会儿。如果它是唯一一个修改num的线程,那么WorkingThreadOuterCritical部分中对num的读取访问应该是安全的——我相信。然而,至少为了可维护性,我不会这么做 **根据我个人的经验,这样的错误很少或从未在调试会话中发生,而是在前180秒内发生关于MPI,我没有任何经验。我几十年前就用过它了,我相信这个事实毫无价值。然而,OP声称
我有一个简单的C++代码来测试和理解线程。 考虑到使用MPI进行多处理以及使用std::thread进行多线程处理本身都是复杂的主题,我将首先将这些主题分开,并在获得每一个主题的一些经验后尝试将它们放在一起
因此,我详细阐述了我认为能够实现的多线程处理 第一个示例是OPs代码的修订版,删除了所有对MPI的引用: 注: 当多线程运行时,变量num被共享,并且变量num在至少一个线程中被修改,每个访问都应该被放入一对互斥锁和解锁中 关键部分应始终尽可能短。一次只能有一个线程通过临界段。因此,它引入了重新序列化,这会消耗并发所需的速度。我在每个线程中引入了一个局部变量num_u,以复制共享变量的当前值,并在各个线程的临界部分之后使用它* 为了更好地说明,我在两个线程中都添加了一个sleep_。没有,我有 总数:10 数目:1 两个线程都完成了。 我觉得有点无聊 输出跳过num==9并打印num==2两次。这在其他跑步中可能会有不同的表现。原因是线程按照定义异步工作。两个线程中相等的100毫秒延迟不是可靠的同步。如果没有任何东西(例如锁定的互斥锁)阻止此操作,则操作系统负责唤醒线程。它可以随时挂起线程 关于mtxNum.lock/mtxNum.unlock:假设临界部分包含比简单的-num更复杂的内容;这可能引发异常。如果引发异常,将跳过mtxNum.unlock,并生成一个阻止任何线程继续的进程 为此,std库提供了一个非常好用的工具: 使用std::lock_-guard的诀窍是,在任何情况下,析构函数都会解锁互斥锁,即使在关键部分内抛出异常也是如此 可能是,我有点偏执,但让我恼火的是,对共享变量的非保护访问可能是偶然发生的,而在任何调试会话或任何编译器诊断中都不会被注意到。**因此,将共享变量隐藏到一个类中可能是值得的,在该类中,只有锁定它才能访问。为此,我在示例中介绍了Shared:#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
template <typename T>
class Shared {
public:
struct Lock {
Shared &shared;
std::lock_guard<std::mutex> lock;
Lock(Shared &shared): shared(shared), lock(shared._mtx) { }
~Lock() = default;
Lock(const Lock&) = delete;
Lock& operator=(const Lock&) = delete;
const T& get() const { return shared._value; }
T& get() { return shared._value; }
};
private:
std::mutex _mtx;
T _value;
public:
Shared() = default;
explicit Shared(T &&value): _value(std::move(value)) { }
~Shared() = default;
Shared(const Shared&) = delete;
Shared& operator=(const Shared&) = delete;
};
typedef Shared<int> SharedInt;
SharedInt shNum(10);
const std::chrono::milliseconds delay(100);
void WorkerFunction()
{
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
int num_;
{ SharedInt::Lock lock(shNum);
num_ = --lock.get();
}
work = num_ != 1;
}
}
int main()
{
std::thread workThread(&WorkerFunction);
int mult = 0;
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
int num_;
{ const SharedInt::Lock lock(shNum);
num_ = lock.get();
}
std::cout << "num: " << num_ << '\n';
mult += mult * num_;
work = num_ != 1;
}
if (workThread.joinable()) workThread.join();
std::cout << "Both threads done.\n";
}
int&num的生存期刚好在SharedInt::Lock lockshNum;的生存期之前结束
当然,我们可以得到一个指向Num的指针,在范围之外使用它,但我会认为这是破坏。< /P> 另一件事,我想提及的是:
原子库为细粒度原子操作提供组件,允许无锁并发编程。对于涉及同一对象的任何其他原子操作,每个原子操作都是不可分割的 虽然互斥锁可能是操作系统内核函数的主题,但可以利用CPU特性进行原子访问,而无需进入内核。这可能会提高速度,并减少操作系统资源的使用 如果resp没有H/W支持,则更好。键入available可根据以下说明返回到基于互斥锁或其他锁定操作的实现: 除std::atomic_标志外的所有原子类型都可以使用互斥锁或其他锁定操作来实现,而不是使用无锁原子CPU指令。原子类型有时也允许是无锁的,例如,如果给定体系结构上只有对齐的内存访问自然是原子的,则相同类型的未对齐对象必须使用锁 使用std::atomic修改的样本: *我在阅读中沉思了一会儿。如果它是唯一一个修改num的线程,那么WorkingThreadOuterCritical部分中对num的读取访问应该是安全的——我相信。然而,至少为了可维护性,我不会这么做**根据我个人的经验,此类错误很少或从未在调试会话中出现,而是在向客户演示的前180秒内出现。您的代码具有未定义的行为。当你访问No.@ GielsGouaIrARDeT.No.VoD在C++标准中的线程中没有作用时,你也可以在main函数中使用互斥体。有些编译器可能会对它做一些特殊的操作,但这将是一个扩展,而不是标准。作为一个旁注,MPI与此无关,并且可以用非MPI程序观察到相同的行为。@ GielsGouaIARDeTe-我说,在C++标准中,线程不具有易失性。并不是说它在C++中没有任何作用。@ GeleSgouaIaLARDET——如果编译器证明它是用波动来实现的,那么就这样做了。
我不想使用那个编译器。尽管如此,volatile并不是可移植代码中线程同步问题的正确答案,对您的问题“您不需要声明volatile int num;吗”的答案是“否”正确的修复已经被给出:正确使用互斥体。你的代码有未定义的行为。当你访问NU.@ GILESGUAIAARARDET时,你也会在主函数中使用互斥体。-在C++标准中线程不具有任何作用。一些编译器可能会用它做一些特殊的事情,但是这将是一个扩展,而不是标准。作为一个旁注,MPI与此无关,并且可以用非MPI程序观察到相同的行为。@ GielsGouaIrARDeT——我说,在C++标准中,波动不具有线程的作用,而不是在C++中没有任何作用。@ GeleSgouaIaLARDET——如果编译器证明它是用波动来实现的,那么应该是这样。然而,volatile并不是可移植代码中线程同步问题的正确答案,而您的问题“您不需要声明volatile int num;吗”的答案是“否”。正确的修复方法已经给出:正确使用互斥。非常感谢您提供的详细答案!!这非常有用,正是我想要了解线程的原因。再次感谢!!非常感谢您提供的详细答案!!这非常有用,正是我想要了解线程的原因。谢谢艾恩!!
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
template <typename T>
class Shared {
public:
struct Lock {
Shared &shared;
std::lock_guard<std::mutex> lock;
Lock(Shared &shared): shared(shared), lock(shared._mtx) { }
~Lock() = default;
Lock(const Lock&) = delete;
Lock& operator=(const Lock&) = delete;
const T& get() const { return shared._value; }
T& get() { return shared._value; }
};
private:
std::mutex _mtx;
T _value;
public:
Shared() = default;
explicit Shared(T &&value): _value(std::move(value)) { }
~Shared() = default;
Shared(const Shared&) = delete;
Shared& operator=(const Shared&) = delete;
};
typedef Shared<int> SharedInt;
SharedInt shNum(10);
const std::chrono::milliseconds delay(100);
void WorkerFunction()
{
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
int num_;
{ SharedInt::Lock lock(shNum);
num_ = --lock.get();
}
work = num_ != 1;
}
}
int main()
{
std::thread workThread(&WorkerFunction);
int mult = 0;
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
int num_;
{ const SharedInt::Lock lock(shNum);
num_ = lock.get();
}
std::cout << "num: " << num_ << '\n';
mult += mult * num_;
work = num_ != 1;
}
if (workThread.joinable()) workThread.join();
std::cout << "Both threads done.\n";
}
{ SharedInt::Lock lock(shNum);
int &num = lock.get();
num_ = --num;
}
#include <iostream>
#include <thread>
#include <atomic>
#include <chrono>
std::atomic<int> num;
const std::chrono::milliseconds delay(100);
void WorkerFunction()
{
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
work = --num != 1;
}
}
int main()
{
num = 10;
std::thread workThread(&WorkerFunction);
int mult = 0;
for (bool work = true; work; std::this_thread::sleep_for(delay)) {
const int num_ = num;
std::cout << "num: " << num_ << '\n';
mult += mult * num_;
work = num_ != 1;
}
if (workThread.joinable()) workThread.join();
std::cout << "Both threads done.\n";
}