使用lambda表达式创建线程时,如何为每个线程提供自己的lambda表达式副本?

时间:2019-07-23 02:44:12

标签: c++ multithreading lambda stdthread

我一直在研究一个程序,该程序基本上使用蛮力向后工作以找到一种使用给定操作集达到给定数量的方法。因此,例如,如果我给出了一组运算+ 5,-7,* 10,/ 3,并且给定的数字为100(*此示例可能不会给出解决方案),要解决的最大移动量(比如说8),它将尝试使用这些操作达到100。这部分使用我在应用程序中测试过的单个线程工作。

但是,我希望它更快,因此我开始使用多线程。我花了很长时间才能使lambda函数正常工作,并且经过认真的调试后才意识到,从技术上来说,找到了“ combo”解决方案。但是,在测试之前,它已被更改。考虑到我认为每个线程都拥有自己的lambda函数副本和要使用的变量,因此我不确定这是如何实现的。

总而言之,程序首先通过解析信息开始,然后将由解析器划分为信息的信息传递给操作对象(有点像函子)的数组。然后使用算法生成组合,然后由操作对象执行组合。为简单起见,该算法吸收大量运算,将其分配给一个char值(每个char值对应于一个运算),然后输出一个char值。它生成所有可能的组合。

这是我的程序如何工作的摘要。除了两件事之外,其他一切似乎都正常运行。我没有添加到标题中,这是另一个错误,因为有一种解决方法,但是我对替代方法感到好奇。这种方式也可能对我的计算机不利。

因此,回到通过线程输入的lambda表达式的问题,正如我在调试器中使用断点所看到的那样。似乎两个线程都没有生成单独的连击,而是更恰当地在第一个数字之间切换,而是交替切换。因此,它将是1111、2211,而不是生成1111、2111(它们是如上一段所示生成的,但是它们一次完成一个char,并使用stringstream组合),但是一旦它们退出循环填满了组合,组合就会迷路。它会在两者之间随机切换,并且永远不会测试正确的组合,因为组合似乎会被随机打乱。我意识到这一定与种族状况和相互排斥有关。我以为我通过不更改从lambda表达式外部更改的任何变量来避免了这一切,但是似乎两个线程都使用相同的lambda表达式。

我想知道为什么会这样,以及如何做到这一点,以便我可以说创建这些表达式的数组并为每个线程分配自己的线程,或者类似于避免整体处理互斥的内容

现在,当我最后删除操作对象数组时,会发生另一个问题。分配它们的代码和删除代码如下所示。

  operation *operations[get<0>(functions)];

  for (int i = 0; i < get<0>(functions); i++)
  {
        //creates a new object for each operation in the array and sets it to the corresponding parameter
        operations[i] = new operation(parameterStrings[i]);
  }
  delete[] operations;

get <0>(functions)是将函数的数量存储在元组中的位置,并且是要存储在数组中的对象的数量。 paramterStrings是一个向量,其中存储了用作类的构造函数的参数的字符串。此代码导致“异常跟踪/断点陷阱”。如果我改用“ * operations”,则会在定义类的文件中出现段错误,该行的第一行显示“类操作”。另一种选择是只注释掉删除部分,但是考虑到使用“ new”运算符创建它并可能导致内存泄漏的事实,我很确定这样做是一个坏主意。

以下是lambda表达式的代码,以及用于创建线程的相应代码。我在lambda表达式中读取了代码,以便可以对其进行查找以找出导致竞争情况的可能原因。

auto threadLambda = [&](int thread, char *letters, operation **operations, int beginNumber) {
int i, entry[len];
        bool successfulComboFound = false;
        stringstream output;
        int outputNum;

        for (i = 0; i < len; i++)
        {
              entry[i] = 0;
        }
        do
        {
              for (i = 0; i < len; i++)
              {
                    if (i == 0)
                    {
                          output << beginNumber;
                    }

                    char numSelect = *letters + (entry[i]);

                    output << numSelect;
              }
              outputNum = stoll(output.str());
              if (outputNum == 23513511)
              {
                    cout << "strange";
              }
              if (outputNum != 0)
              {
                    tuple<int, bool> outputTuple;
                    int previousValue = initValue;
                    for (int g = 0; g <= (output.str()).length(); g++)
                    {
                          operation *copyOfOperation = (operations[((int)(output.str()[g])) - 49]);

                          //cout << copyOfOperation->inputtedValue;

                          outputTuple = (*operations)->doOperation(previousValue);
                          previousValue = get<0>(outputTuple);

                          if (get<1>(outputTuple) == false)
                          {
                                break;
                          }
                          debugCheck[thread - 1] = debugCheck[thread - 1] + 1;
                          if (previousValue == goalValue)
                          {
                                movesToSolve = g + 1;
                                winCombo = outputNum;
                                successfulComboFound = true;
                                break;
                          }
                    }
                    //cout << output.str() << ' ';
              }
              if (successfulComboFound == true)
              {
                    break;
              }
              output.str("0");

              for (i = 0; i < len && ++entry[i] == nbletters; i++)
                    entry[i] = 0;
        } while (i < len);

        if (successfulComboFound == true)
        {
              comboFoundGlobal = true;
              finishedThreads.push_back(true);
        }
        else
        {
              finishedThreads.push_back(true);
        }
  };

在此处创建线程:

  thread *threadArray[numberOfThreads];

  for (int f = 0; f < numberOfThreads; f++)
  {
        threadArray[f] = new thread(threadLambda, f + 1, lettersPointer, operationsPointer, ((int)(workingBeginOperations[f])) - 48);
  }

如果需要更多代码来帮助解决问题,请告诉我,我将编辑帖子以添加代码。预先感谢您的所有帮助。

1 个答案:

答案 0 :(得分:0)

您的lambda对象通过引用[&]捕获其参数,因此线程使用的lambda的每个副本都引用 same 共享对象,因此各种线程相互竞争和破坏。

这是假设movesToSolvewinCombo之类的东西来自捕获(虽然代码不清楚,但看起来很像)。发现成功的结果后,winCombo会更新,但是另一个线程可能会在此之后立即将其覆盖。

因此,每个线程都使用相同的数据,数据争用不胜枚举。

您要确保lambda仅对两种三种类型的数据起作用:

  1. 私人数据
  2. 共享的恒定数据
  3. 正确同步的可变共享数据

通常,您希望类别1和2中的几乎所有内容,而类别3中的内容尽可能少。

类别1是最简单的,因为您可以在lambda函数中使用例如局部变量,或者如果确保将不同的lambda实例传递给每个线程,则可以使用按值捕获变量。

对于类别2,您可以使用const来确保不修改相关数据。

最后,您可能需要 some 个共享的全局状态,例如,表明已找到一个值。一种选择是类似于单个std::atomic<Result *>,其中任何线程找到结果时,它们都会创建一个新的Result对象,并将其原子地进行比较并交换为全局可见的结果指针。其他线程在其运行循环中检查此指针是否为null,以查看是否应尽早纾困(我想这就是您想要的:如果任何线程找到结果,所有线程都将完成)。

更惯用的方法是使用std::promise