当使用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

当使用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

如果我注释掉那个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 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";
}