使用 lambda 表达式创建线程时,如何为每个线程提供自己的 lambda 表达式副本?
When creating threads using lambda expressions, how to give each thread its own copy of the lambda expression?
我一直在研究一个程序,该程序基本上使用蛮力向后工作以找到使用给定操作集达到给定数字的方法。因此,例如,如果我给出了一组操作 +5、-7、*10、/3,并且给定的数字是 100(*这个例子可能不会得出解决方案),还有一个给定的要解决的最大移动量(假设为 8),它将尝试使用这些操作来达到 100。这部分使用我在应用程序中测试过的单个线程工作。
但是,我希望它更快,所以我使用了多线程。我已经工作了很长时间,甚至让 lambda 函数工作,经过一些认真的调试后,我意识到在技术上找到了解决方案 "combo"。然而,在它被测试之前,它被改变了。考虑到我认为每个线程都有自己的 lambda 函数副本及其要使用的变量,我不确定这怎么可能。
综上所述,程序从解析信息开始,然后将解析器划分的信息作为参数传递给操作数组object(有点像函子)。然后它使用生成组合的算法,然后由操作 objects 执行。简单来说,该算法接收操作量,将其分配给一个 char 值(每个 char 值对应一个操作),然后输出一个 char 值。它生成所有可能的组合。
这是我的程序如何工作的总结。除了两件事之外,一切似乎都运行良好且井然有序。还有一个错误我没有添加到标题中,因为有一种方法可以修复它,但我对替代方法很好奇。这种方式也可能不适合我的电脑。
因此,回到使用线程输入的 lambda 表达式的问题,正如我在调试器中使用断点所看到的那样。看起来两个线程都没有生成单独的组合,而是更恰当地在第一个数字之间切换,而是交替组合。因此,它会生成 1111、2211,而不是生成 1111、2111。(这些生成如前一段所示,但它们一次完成一个字符,使用字符串流组合),但是一旦它们退出循环填满连击,连击会丢失。它会在两者之间随机切换,并且永远不会测试正确的组合,因为组合似乎是随机打乱的。我意识到这一定与竞争条件和互斥有关。我原以为我已经通过不更改从 lambda 表达式外部更改的任何变量来避免了这一切,但看起来两个线程都在使用相同的 lambda 表达式。
我想知道为什么会发生这种情况,以及如何做到这一点,以便我可以说创建这些表达式的数组并为每个线程分配自己的线程,或者类似于避免必须处理互斥的东西一个整体。
现在,另一个问题发生在我最后删除我的操作数组 objects 时。赋值代码和删除代码如下所示。
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) 是函数数量存储在元组中的地方,也是要存储在数组中的 objects 的数量。 paramterStrings 是一个向量,其中存储用作 class 构造函数参数的字符串。此代码导致 "Exception trace/breakpoint trap." 如果我使用“*operations”,我在定义 class 的文件中出现分段错误,第一行显示 "class operation." 替代方案是只是为了注释掉删除部分,但我很确定这样做不是一个好主意,因为它是使用 "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);
}
如果需要更多代码来帮助解决问题,请告诉我,我将编辑 post 以添加代码。预先感谢您的所有帮助。
您的 lambda 对象通过引用 [&]
捕获其参数,因此线程使用的每个 lambda 副本都引用 相同 个共享对象,因此各种线程争用互相攻击。
这是假设 movesToSolve
和 winCombo
之类的东西来自捕获(从代码中看不清楚,但看起来是这样)。 winCombo
在找到成功结果时更新,但另一个线程可能会立即覆盖它。
所以每个线程都使用相同的数据,数据竞争比比皆是。
您想确保您的 lambda 仅适用于两种三种类型的数据:
- 私人数据
- 共享的常量数据
- 正确同步可变共享数据
一般来说,您希望拥有类别 1 和 2 中的几乎所有内容,而类别 3 中的内容尽可能少。
类别 1 是最简单的,因为您可以在 lambda 函数中使用例如局部变量,或者如果您确保将不同的 lambda 实例传递给每个线程,则可以使用 captured-by-value 变量。
对于类别 2,您可以使用 const
来确保相关数据不被修改。
最后,您可能需要 一些 共享全局状态,例如,指示找到一个值。一种选择类似于单个 std::atomic<Result *>
,当任何线程找到结果时,它们会创建一个新的 Result
对象,并以原子方式 compare-and-swap 将其放入全局可见的结果指针中。其他线程在它们的 运行 循环中检查此指针是否为 null 以查看它们是否应该提前退出(我假设这就是您想要的:如果任何线程找到结果,所有线程都将完成)。
更惯用的方法是使用 std::promise
.
我一直在研究一个程序,该程序基本上使用蛮力向后工作以找到使用给定操作集达到给定数字的方法。因此,例如,如果我给出了一组操作 +5、-7、*10、/3,并且给定的数字是 100(*这个例子可能不会得出解决方案),还有一个给定的要解决的最大移动量(假设为 8),它将尝试使用这些操作来达到 100。这部分使用我在应用程序中测试过的单个线程工作。
但是,我希望它更快,所以我使用了多线程。我已经工作了很长时间,甚至让 lambda 函数工作,经过一些认真的调试后,我意识到在技术上找到了解决方案 "combo"。然而,在它被测试之前,它被改变了。考虑到我认为每个线程都有自己的 lambda 函数副本及其要使用的变量,我不确定这怎么可能。
综上所述,程序从解析信息开始,然后将解析器划分的信息作为参数传递给操作数组object(有点像函子)。然后它使用生成组合的算法,然后由操作 objects 执行。简单来说,该算法接收操作量,将其分配给一个 char 值(每个 char 值对应一个操作),然后输出一个 char 值。它生成所有可能的组合。
这是我的程序如何工作的总结。除了两件事之外,一切似乎都运行良好且井然有序。还有一个错误我没有添加到标题中,因为有一种方法可以修复它,但我对替代方法很好奇。这种方式也可能不适合我的电脑。
因此,回到使用线程输入的 lambda 表达式的问题,正如我在调试器中使用断点所看到的那样。看起来两个线程都没有生成单独的组合,而是更恰当地在第一个数字之间切换,而是交替组合。因此,它会生成 1111、2211,而不是生成 1111、2111。(这些生成如前一段所示,但它们一次完成一个字符,使用字符串流组合),但是一旦它们退出循环填满连击,连击会丢失。它会在两者之间随机切换,并且永远不会测试正确的组合,因为组合似乎是随机打乱的。我意识到这一定与竞争条件和互斥有关。我原以为我已经通过不更改从 lambda 表达式外部更改的任何变量来避免了这一切,但看起来两个线程都在使用相同的 lambda 表达式。
我想知道为什么会发生这种情况,以及如何做到这一点,以便我可以说创建这些表达式的数组并为每个线程分配自己的线程,或者类似于避免必须处理互斥的东西一个整体。
现在,另一个问题发生在我最后删除我的操作数组 objects 时。赋值代码和删除代码如下所示。
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) 是函数数量存储在元组中的地方,也是要存储在数组中的 objects 的数量。 paramterStrings 是一个向量,其中存储用作 class 构造函数参数的字符串。此代码导致 "Exception trace/breakpoint trap." 如果我使用“*operations”,我在定义 class 的文件中出现分段错误,第一行显示 "class operation." 替代方案是只是为了注释掉删除部分,但我很确定这样做不是一个好主意,因为它是使用 "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);
}
如果需要更多代码来帮助解决问题,请告诉我,我将编辑 post 以添加代码。预先感谢您的所有帮助。
您的 lambda 对象通过引用 [&]
捕获其参数,因此线程使用的每个 lambda 副本都引用 相同 个共享对象,因此各种线程争用互相攻击。
这是假设 movesToSolve
和 winCombo
之类的东西来自捕获(从代码中看不清楚,但看起来是这样)。 winCombo
在找到成功结果时更新,但另一个线程可能会立即覆盖它。
所以每个线程都使用相同的数据,数据竞争比比皆是。
您想确保您的 lambda 仅适用于两种三种类型的数据:
- 私人数据
- 共享的常量数据
- 正确同步可变共享数据
一般来说,您希望拥有类别 1 和 2 中的几乎所有内容,而类别 3 中的内容尽可能少。
类别 1 是最简单的,因为您可以在 lambda 函数中使用例如局部变量,或者如果您确保将不同的 lambda 实例传递给每个线程,则可以使用 captured-by-value 变量。
对于类别 2,您可以使用 const
来确保相关数据不被修改。
最后,您可能需要 一些 共享全局状态,例如,指示找到一个值。一种选择类似于单个 std::atomic<Result *>
,当任何线程找到结果时,它们会创建一个新的 Result
对象,并以原子方式 compare-and-swap 将其放入全局可见的结果指针中。其他线程在它们的 运行 循环中检查此指针是否为 null 以查看它们是否应该提前退出(我假设这就是您想要的:如果任何线程找到结果,所有线程都将完成)。
更惯用的方法是使用 std::promise
.