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

When creating threads using lambda expressions, how to give each thread its own copy of the lambda expression?

本文关键字:表达式 线程 lambda 自己的 副本 创建 使用      更新时间:2023-10-16

我一直在研究一个程序,它基本上使用蛮力向后工作,以使用给定的一组操作找到一种方法来达到给定的数字。因此,例如,如果我给出一组运算 +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<0>(函数)是函数数量存储在元组中的位置,也是要存储在数组中的对象数。paramterString 是一个向量,其中存储了用作类构造函数参数的字符串。此代码会导致"异常跟踪/断点陷阱"。如果我使用"*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);
}

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

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

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

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

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

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

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

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

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

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

一种更惯用的方法是使用std::promise.