简体   繁体   English

将子线程同步到由父线程管理的原子时间

[英]Synchronizing child threads to atomic time managed by parent

I am trying to write a simulation where different threads need to perform a given calculation on a thread-specific interval (in the minimal example here that interval is between 1 and 4) based on an atomic simulation time managed by a parent thread.我正在尝试编写一个模拟,其中不同的线程需要根据父线程管理的原子模拟时间在特定于线程的间隔(在此最小示例中,间隔在 1 和 4 之间)执行给定计算。


The idea is to have the parent advance the simulation by a single time step (in this case always 1 for simplicity) and then have all the threads independently check if they need to do a calculation and once they have checked decrement an atomic counter and wait until the next step.这个想法是让父进程将模拟推进一个时间步长(在这种情况下,为简单起见,始终为 1),然后让所有线程独立检查它们是否需要进行计算,一旦检查完毕,递减一个原子计数器并等待直到下一步。 I expect that after running this code the number of calculations for each thread would be exactly the length of the simulation (ie 10000 steps) divided by the thread-specific interval (so for thread interval of 4 the thread should do exactly 2500 calculations.我期望运行此代码的计算次数为每个线程将是线程特定间隔除以模拟(即10000步)的完全长度后(所以对的4线间隔的线程应做的正是2500计算。

#include <thread>
#include <iostream>
#include <atomic>

std::atomic<int> simTime;
std::atomic<int> tocalc;
int end = 10000;

void threadFunction(int n);

int main() {
  int nthreads = 4;
  std::thread threads[nthreads];
  for (int ii = 0; ii < nthreads; ii ++) {
    threads[ii] = std::thread(threadFunction, ii+1);
  }

  simTime = 0;
  tocalc = 0;
  while (simTime < end) {
    tocalc = nthreads - 1;
    simTime += 1;
    // do calculation
    while (tocalc > 0) {
      // wait until all the threads have done their calculation
      // or at least checked to see if they need to
    }
  }

  for (int ii = 0; ii < nthreads; ii ++) {
    threads[ii].join();
  }
}

void threadFunction(int n) {
  int prev = simTime;
  int fix = prev;
  int ncalcs = 0;
  while (simTime < end) {
    if (simTime - prev > 0) {
      prev = simTime;
      if (simTime - fix >= n) {
        // do calculation
        ncalcs ++;
        fix = simTime;
      }
      tocalc --;
    }
  }
  std::cout << std::to_string(n)+" {ncalcs} - "+std::to_string(ncalcs)+"\n";
}

However, the output is not consistent with that expectation, one possible output is但是,输出与该期望不一致,一种可能的输出是

2 {ncalcs} - 4992
1 {ncalcs} - 9983
3 {ncalcs} - 3330
4 {ncalcs} - 2448

While the expected output is虽然预期的输出是

2 {ncalcs} - 5000
1 {ncalcs} - 10000
3 {ncalcs} - 3333
4 {ncalcs} - 2500

I am wondering if anyone has insight as to why this method of forcing the threads to wait for the next step seems to be failing - if it is perhaps a simple issue with my code or if it is a more fundamental problem with the approach.我想知道是否有人知道为什么这种强制线程等待下一步的方法似乎失败了 - 如果这可能是我的代码的一个简单问题,或者它是否是该方法的一个更基本的问题。 Any insight is appreciated, thanks.任何见解表示赞赏,谢谢。


Note笔记

I am using this approach because the overhead for other methods I have tried (eg using pipes , joining at each step) is prohibitively expensive, if there is a less expensive way of communicating between the threads I am open to such suggestions.我使用这种方法是因为我尝试过的其他方法(例如使用pipes ,在每个步骤中加入)的开销非常昂贵,如果在线程之间有一种更便宜的通信方式,我愿意接受这些建议。

To expand on the comments, initializing tocalc to nthreads - 1 means that on some of the iterations all the child threads will decrement tocalc before the parent thread evaluates it - the reads and writes to an atomic are handled by the memory scheduler.为了扩展注释,将tocalc初始化为nthreads - 1意味着在某些迭代中,所有子线程将在父线程对其进行评估之前递减tocalc - 对atomic的读取和写入由内存调度程序处理。 So sometimes the sequence could go所以有时顺序可能会去

  • Child 1 decrements tocalc , new value is 2子 1 递减到tocalc ,新值为 2
  • Child 3 decrements tocalc , new value is 1 Child 3 递减到tocalc ,新值为1
  • Child 4 decrements tocalc , new value is 0 Child 4 递减到tocalc ,新值为0
  • Child 2 decrements tocalc , new value is -1 Child 2 递减到tocalc ,新值为-1
  • Parent evaluates if tocalc > 0 , returns false - simulation advances父级评估是否tocalc > 0 ,返回 false - 模拟推进

and other times the parent evaluation could be scheduled before the last thread decrements tocalc , ie其他时候可以在最后一个线程递减到tocalc之前安排父评估,即

  • Child 1 decrements tocalc , new value is 2子 1 递减到tocalc ,新值为 2
  • Child 3 decrements tocalc , new value is 1 Child 3 递减到tocalc ,新值为1
  • Child 4 decrements tocalc , new value is 0 Child 4 递减到tocalc ,新值为0
  • Parent evaluates if tocalc > 0 , returns false - simulation advances父级评估是否tocalc > 0 ,返回 false - 模拟推进
  • Child 2 decrements tocalc , new value is 2子 2 递减到tocalc ,新值为 2

in which case child thread number 2 will miss an iteration.在这种情况下,子线程编号 2 将错过一次迭代。 Since this doesn't happen every time due to the semi-randomness of the scheduling order the total number of misses is not a linear function of the number of threads, but some small fraction of the total iterations.由于由于调度顺序的半随机性,这不会每次都发生,因此未命中总数不是线程数的线性函数,而是总迭代次数的一小部分。 If you modify the code to the below it will produce the desired result.如果您将代码修改为以下内容,它将产生所需的结果。

#include <thread>
#include <iostream>
#include <atomic>

std::atomic<int> simTime;
std::atomic<int> tocalc;
int end = 10000;

void threadFunction(int n);

int main() {
    int nthreads = 4;
    simTime = 0;
    tocalc = 0;
    std::thread threads[nthreads];
    for (int ii = 0; ii < nthreads; ii ++) {
        threads[ii] = std::thread(threadFunction, ii+1);
    }

    int wait = 0;
    while (simTime < end) {
        tocalc = nthreads;
        simTime += 1;
        // do calculation
        while (tocalc > 0) {
            // wait until all the threads have done their calculation
            // or at least checked to see if they need to
        }
    }
    for (int ii = 0; ii < nthreads; ii ++) {
        threads[ii].join();
    }
}

void threadFunction(int n) {
    int prev = 0;
    int fix = prev;
    int ncalcs = 0;
    while (simTime < end) {
        if (simTime - prev > 0) {
            prev = simTime;
            if (simTime - fix >= n) {
                // do calculation
                ncalcs ++;
                fix = simTime;
            }
            tocalc --;
        }
    }
    std::cout << std::to_string(n)+" {ncalcs} - "+std::to_string(ncalcs)+"\n";
}

And one possible output would be (order of thread completion is somewhat random)一种可能的输出是(线程完成的顺序有点随机)

2 {ncalcs} - 5000
3 {ncalcs} - 3333
1 {ncalcs} - 10000
4 {ncalcs} - 2500

Using a similar setup I noticed that not every thread will reach the number you expect it to, but only be off by one.使用类似的设置,我注意到并非每个线程都会达到您期望的数量,但只会减少一个。 ie IE

2 {ncalcs} - 4999
4 {ncalcs} - 2500
1 {ncalcs} - 9999
3 {ncalcs} - 3333

Or the like, seemingly random with regard to thread and number of threads for which it happens.或者类似的,就线程和发生这种情况的线程数量而言似乎是随机的。 Though I'm not sure what is causing it I thought it might be good to issue a warning, you can get around it by checking if simTime - fix == 0 and if it isn't then do another calculation before quitting.虽然我不确定是什么导致了它,但我认为发出警告可能会很好,您可以通过检查simTime - fix == 0它,如果不是,则在退出之前再进行一次计算。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM