【问题标题】:When creating threads using lambda expressions, how to give each thread its own copy of the lambda expression?使用 lambda 表达式创建线程时,如何给每个线程自己的 lambda 表达式副本?
【发布时间】:2019-07-23 02:44:12
【问题描述】:

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

但是,我希望它更快,所以我开始使用多线程。我已经工作了很长时间,甚至让 lambda 函数工作,经过一些认真的调试后,我意识到解决方案“组合”在技术上是找到的。然而,在它被测试之前,它被改变了。考虑到我认为每个线程都有自己的 lambda 函数副本及其要使用的变量,我不确定这怎么可能。

总之,程序从解析信息开始,然后将解析器划分的信息作为参数传递到操作对象(有点像函子)的数组中。然后它使用一种算法生成组合,然后由操作对象执行。简单来说,该算法接受操作量,将其分配给一个 char 值(每个 char 值对应一个操作),然后输出一个 char 值。它生成所有可能的组合。

这是我的程序如何工作的总结。除了两件事之外,一切似乎都运行良好且井然有序。还有一个错误我没有添加到标题中,因为有一种方法可以修复它,但我对替代方案很好奇。这种方式也可能对我的电脑不好。

所以,回到与线程一起输入的 lambda 表达式的问题,正如我在调试器中使用断点所看到的那样。似乎两个线程都没有生成单独的连击,而是更正确地在第一个数字之间切换,而是交替连击。因此,它将转到 1111、2211,而不是生成 1111、2111。(这些生成如上一段所示,但它们一次完成一个字符,使用字符串流组合),但一旦它们退出循环将连击填满,连击就会丢失。它会在两者之间随机切换,并且永远不会测试正确的组合,因为组合似乎是随机打乱的。我意识到这一定与竞争条件和互斥有关。我原以为我已经通过不更改从 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(functions) 是将函数的数量存储在元组中的位置,并且是要存储在数组中的对象的数量。 paramterStrings 是一个向量,其中存储了用作类的构造函数的参数的字符串。此代码导致“异常跟踪/断点陷阱”。如果我改用“*操作”,我会在定义类的文件中出现分段错误,第一行显示“类操作”。另一种方法是注释掉删除部分,但考虑到它是使用“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);
  }

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

【问题讨论】:

  • 虽然你已经包含了你的代码很好,但你最好创建一个 minimal 示例,它说明了你在使用 lambda 时遇到的问题,而不需要一个巨大的介绍和不仅仅是一屏代码。
  • 我会尝试,但老实说,我不太确定 Lambda 表达式等需要哪些代码,因为这是我第一次在多线程中使用 Lambda 表达式一个程序。另外,我知道介绍很大,我将尝试对其进行删节。谢谢:)
  • 好吧,你展示的代码太多也太少了。从某种意义上说,我们看不到 lambda 捕获的内容以及 lenmovesToSolvecomboFoundGlobal 等内容的来源。
  • comboFoundGlobal 只是在 int main() 的开头初始化为 false boolean,len 就是 move - 1,moves 是开头输入的移动数量,movesToSolve 也就是在程序开始时初始化为 0 的 int。
  • 这些只是示例,您的散文描述不一定有帮助,因为现在我需要知道它们是否是全局变量等。这就是您需要 MCVE 的原因。

标签: c++ multithreading lambda stdthread


【解决方案1】:

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

这是假设 movesToSolvewinCombo 之类的内容来自捕获(从代码中不清楚,但似乎是这样)。 winCombo 会在找到成功的结果时更新,但另一个线程可能会立即覆盖它。

所以每个线程都使用相同的数据,数据竞争比比皆是。

您希望确保您的 lambda 仅适用于两种三种类型的数据:

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

一般来说,您希望几乎所有东西都属于第 1 类和第 2 类,尽可能少地属于第 3 类。

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

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

最后,您可能需要 一些 共享全局状态,例如,指示找到了一个值。一种选择类似于单个std::atomic&lt;Result *&gt;,当任何线程找到结果时,它们会创建一个新的Result 对象并将其自动比较并交换到全局可见的结果指针中。其他线程在他们的运行循环中检查这个指针是否为空,看看他们是否应该提前退出(我假设这就是你想要的:如果任何线程找到结果,所有线程都会完成)。

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

【讨论】:

  • 通过 lambda 对象,您是指包含 lambda 函数的类/结构,还是在 int main() 函数中,就像我所做的那样,只是一个自动 lambda 函数。此外,正在更改的数据都存储在 lambda 函数内的私有变量中(即存储要检查的当前组合的输出字符串流值)。使用共享常量数据,所有数据几乎都基于通过 cin 输入的用户数据,所以我不确定这将如何工作。
  • @NermalSkywalker - 在这种情况下,lambda 对象是 threadLambda
  • 您谈到了不同的 lambda 实例。这怎么可能,因为我认为这可能是解决我遇到的问题的方法。
  • @NermalSkywalker - 您可以像任何其他对象一样创建 lambda 对象的副本,方法是将其分配给新变量,例如,auto l2 = l; 创建 lambda l2 的副本。但是,我只是仔细检查了 std::thread 构造函数,它已经创建了传入函数的副本(在您的情况下为 lambda),因此该部分是伪造的。每个线程已经有一个 lambda。但是,它们都共享相同的状态,因为您通过引用 [&amp;] 捕获值。你需要修复的那部分。
  • 感谢您的建议终于让它工作了 :)
猜你喜欢
  • 2015-09-06
  • 1970-01-01
  • 2013-01-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多