从另一个对象指向线程对象:危险

Pointing to thread object from another object: dangerous?

本文关键字:对象 危险 线程 一个对象      更新时间:2023-10-16

假设有两个对象都继承自thread父类"使用pthreads的utility thread "

class Othread1: public thread
{
public:
  start() { /* launch thread at 10 Hz */ };
  end();
  void setvar(float vr) {var2= vr } ;
protected :
  float var1;
}

class Othread2: public thread
{
   start()  { /* launch the thread at 1000 Hz */ } ;
   end();
   float getvar() { return var2 } ;
protected :
   float var2;
}

有这样一种东西我们可以这样做吗?

void threadManager(thread *th1, thread *th2)
{
  float vtemp = th2->getvar();
  th1->setvar(vtemp);
}
int main ()
{
  thread th1;
  thread th2;
  threadManager(&th1,&th2);
  return 0;
}

这样的线程间数据使用是一个安全的事情吗?或者我必须使用生产者/消费者模式进行队列来交换数据吗?

我仍然不完全确定你在做什么,但这里有一个例子,希望能帮助你。

如果你想在一个线程中读取数据,同时在另一个线程中写入数据,你需要同步,否则你会调用未定义的行为。"写作事件"answers"阅读事件"之间的时间长短并不重要。就语言规则而言,两个同步点之间发生的一切都是"同时的"。

这方面的明确规则可以在§1.10 [intro]中找到。多线程]的N4140, c++ 14标准的最终草案。但那里使用的语言很难破译。

一个更非正式的解释可以在Bjarne Stroustrup的 c++编程语言(第4版)的§41.2.4中找到。

如果两个线程可以同时访问一个内存位置,并且至少有一个访问是写操作,那么两个线程就存在数据竞争。请注意,精确地定义"同时"并不简单。如果两个线程有数据竞争,没有语言保证:行为是未定义的。

就我而言,我认为第一句中的"can"是假的,不应该出现在那里,但我引用的是这本书的原样。

保护互访问的经典方法是使用mumus和锁。由于c++ 11(并且仅从c++ 11开始,c++才有了并发性的定义),标准库为此提供了std::mutexstd::lock_guard(都在<mutex>头文件中定义)。

如果您有简单的类型,如整数,使用锁是多余的。现代硬件支持对这些简单类型进行原子操作。标准库为此提供了std::atomic类模板(在<atomic>头文件中定义)。你可以在任何普通的可复制类型上使用它。

这是一个相当无用的例子,我们有两个线程分别执行一个函数writerreaderwriter有一个伪随机数生成器,并定期要求它生成一个新的随机整数,并自动存储在全局变量value中。reader周期性地自动加载value的值,并推进它自己的伪随机数生成器,直到它赶上。第二个全局原子变量done由主线程使用,在两个线程应该停止时向它们发出信号。注意,我已经把你的赫兹换成了千赫兹,这样等待程序执行就不那么无聊了。

#include <atomic>
#include <chrono>
#include <random>
#include <thread>

namespace /* anonymous */
{
  std::atomic<bool> done {};
  std::atomic<int> value {};
  void
  writer(const std::chrono::microseconds period)
  {
    auto rndeng = std::default_random_engine {};
    auto rnddst = std::uniform_int_distribution<int> {};
    while (!done.load())
      {
        const auto next = rnddst(rndeng);
        value.store(next);
        std::this_thread::sleep_for(period);
      }
  }
  void
  reader(const std::chrono::microseconds period)
  {
    auto rndeng = std::default_random_engine {};
    auto rnddst = std::uniform_int_distribution<int> {};
    auto last = 0;
    while (!done.load())
      {
        const auto next = value.load();
        while (last != next)
          last = rnddst(rndeng);
        std::this_thread::sleep_for(period);
      }
  }
}

int
main()
{
  using namespace std::chrono_literals;
  std::thread writer_thread {writer, 100us};  //  10 kHz
  std::thread reader_thread {reader,  10us};  // 100 kHz
  std::this_thread::sleep_for(3s);
  done.store(true);
  writer_thread.join();
  reader_thread.join();
}

如果你有一个现代的GCC或Clang,你可以(也许应该)用-fsanitize=thread开关编译你的调试版本。如果运行这样编译的二进制文件,并且它执行数据竞争,编译器添加的特殊工具将输出有用的错误消息。试着用一个普通的int value替换上面程序中的std::atomic<int> value,看看工具会报告什么。

如果你还没有c++ 14,你不能使用字面后缀,但必须拼出std::chrono::microseconds {10}等等。