原子与变量通过ref传递。在<thread> </thread>中

时间:2014-09-08 21:27:53

标签: multithreading c++11 pthreads shared-memory atomic

我想编写一个程序,其中将创建随机数,我将追踪其中最大的一个。三个线程将并行运行。

我用两种方法做到了。首先,我在main()中创建一个变量,然后我通过ref传递。每个线程。最后,此变量保持生成的最大值。当变量更新时,我使用互斥锁(我真的需要吗?)。

第二种方法使用std :: atomic并产生相同的结果(据我测试它)。

这是我做的一个小例子,为了在我的项目中使用 critical ,所有线程都可以看到所有线程找到的当前最佳值。

代码:

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex
#include <atomic>
#include <random>

std::default_random_engine generator((unsigned int)time(0));
int random(int n) {
  std::uniform_int_distribution<int> distribution(0, n);
  return distribution(generator);
}

std::mutex mtx;           // mutex for critical section
std::atomic<int> at_best(0);

void update_cur_best(int& cur_best, int a, int b) {
  // critical section (exclusive access to std::cout signaled by locking mtx):
  if(cur_best > a && cur_best > b)
    return;
  if(at_best > a && at_best > b)
        return;
  int best;
  if(a > b)
    best = a;
  else
    best = b;
  mtx.lock();
  cur_best = best;
  mtx.unlock();


  // or

  if(a > b)
    at_best = a;
  else
    at_best = b;
}

void run(int max, int& best) {
    for(int i = 0; i < 15; ++i) {
        update_cur_best(best, random(max), random(max));
    }
}

//g++ -std=c++0x -pthread px.cpp -o px
int main ()
{
  int best = 0;
  std::thread th1 (run, 100, std::ref(best));
  std::thread th2 (run, 100, std::ref(best));
  std::thread th3 (run, 100, std::ref(best));

  th1.join();
  th2.join();
  th3.join();

  std::cout << "best = " << best << std::endl;
  std::cout << "at_best = " << at_best << std::endl;

  return 0;
}

问题:

  1. 这两种方法是否相同?

    来自 ref:“原子 types是封装其访问受保证的值的类型 不会导致数据争用,可用于同步内存 访问不同的线程。“

    它们是否等同于 他们产生的结果和效率?

  2. 如果是,那为什么要引入原子?我应该采用什么方法 使用?速度是我感兴趣的。

  3. 有没有更快的方法来实现此功能?

  4. 请记住,对于我的实际项目,重点是best将具有来自所有线程的当前最佳值,以便更轻松地进行比较。

2 个答案:

答案 0 :(得分:2)

除了the data race @nwp pointed out in the comments之外,您还在generatordistribution上进行了数据争用:标准随机数生成器和发行版不会被多个线程同时更新。通过为每个线程创建随机数生成器/分发,可以轻松解决此问题。您还需要确保全局“当前最大值”的比较和更新是原子的,例如,使用compare_exchange_weakLive at Coliru):

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <atomic>
#include <random>
#include <array>

void update_cur_best(std::atomic<int>& best, int a, int b) {
  if (a < b) {
    a = b;
  }

  auto cur_best = best.load(std::memory_order_relaxed);
  while (cur_best < a && !best.compare_exchange_weak(cur_best, a))
    ;
}

void run(int max, std::atomic<int>& best) {
  std::mt19937 generator{std::random_device{}()};
  std::uniform_int_distribution<int> distribution{0, max};

  for(int i = 0; i < 15; ++i) {
    update_cur_best(best, distribution(generator), distribution(generator));
  }
}

//g++ -std=c++0x -pthread px.cpp -o px
int main()
{
  std::atomic<int> best{0};
  const int max = 100;
  std::array<std::thread, 3> threads;

  for (auto& t : threads) {
      t = std::thread(run, max, std::ref(best));
  }

  for (auto& t : threads) {
      t.join();
  }

  std::cout << "best = " << best << std::endl;
}

更新魔法在compare_exchange_weak循环中:

  auto cur_best = best.load(std::memory_order_relaxed);
  while (cur_best < a && !best.compare_exchange_weak(cur_best, a))
    ;

当且仅当前值为best时,它会将a的值更新为cur_best。否则,cur_best会更新为当前值best,循环会再次尝试。实际上,best只有在其他线程在此线程尝试更新之前未将其设置为大于a的值时才会更新。

答案 1 :(得分:0)

就使用互斥锁而言:您正在读取互斥锁之外的cur_best并在持有互斥锁的情况下更新它 - 这不会起作用。

使用原子:

虽然读取或更新原子变量对于那些同时发生的事情是原子的,但每个单独的读取或更新都是原子的 - 原子性并不适用于不同的表达式。

因此,例如,if(at_best > a && at_best > b)中的表达式可能会解析为true(并且我忽略了at_best每次都可以返回不同值的事实在线程执行时,#39;在该表达式中进行评估:

if(a > b)
    at_best = a;
else
    at_best = b;

原子变量at_best可能已被另一个线程更新,而这个可能会用较小的值覆盖它。现在,另一个线程设置的较大值将永远丢失。