指向另一个对象的线程对象:危险吗?

时间:2015-10-10 20:08:10

标签: c++ multithreading concurrency

假设有两个对象都继承自thread父类“使用pthreads的实用程序线程”。

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;
}

这样的线程数据是否使用安全的方法?或者我是否必须使用生产者/消费者模式进行队列交换数据?

1 个答案:

答案 0 :(得分:2)

我仍然不完全确定你要做什么,但这里有一个例子可以帮助你。

如果要读取同时在另一个线程中写入的一个线程中的数据,则需要同步,否则您将调用未定义的行为。 “写作事件”和“阅读事件”之间是否有很多或很少的时间并不重要。就语言规则而言,两个同步点之间发生的所有事情都是“同时发生”。

对此的明确规则可以在N4140的§1.10[intro.multithreaded]中找到,这是C ++ 14标准的最终草案。但那里使用的语言很难破译。

更为非正式的解释可以在Bjarne Stroustrup的 C ++编程语言(第4版)的第41.2.4节中找到。

  

如果两个线程都可以同时访问内存位置并且至少其中一个访问是写入,则两个线程具有数据争用。请注意,精确定义“同时”并非易事。如果两个线程有​​数据争用,则没有语言保证保持:行为未定义。

就我而言,我认为第一句中的“可以”是假的,不应该在那里,但我按原样引用这本书。

保护互访的经典方法是使用mutices和lock。从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开关编译您的调试版本。如果运行如此编译的二进制文件并执行数据竞争,则编译器添加的特殊工具将输出有用的错误消息。尝试使用普通std::atomic<int> value替换上述程序中的int value,然后查看该工具将报告的内容。

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