2012-09-17 44 views
7

我在lambda表达式中有一个带有循环变量参数的for循环中的lambda。当我运行它时,我期望输出数字0-9。但由于它是一个lambda,所以x不会立即得到评估。Lambda循环内

for(int x = 0; x < n; ++x) 
    { 
      vec.push_back(thread{[&x](){ 
        m.lock(); 
        cout << x << endl; 
        m.unlock(); 
      }}); 
    } 

输出:

0 
3 
3 
9 

对其他语言的解决方案是创建一个临时变量,

for(int x = 0; x < n; ++x) 
    { 
      int tmp = x; 
      vec.push_back(thread{[&tmp](){ 
        m.lock(); 
        cout << tmp << endl; 
        m.unlock(); 
      }}); 
    } 

,但似乎并没有工作。

看到Threads receiving wrong parameters

奖励:

我在寻找一个答案,我在这建议不要使用会使无效的迭代器的容器这个问题 Generalizing C++11 Threads class to work with lambda 跌跌撞撞。为什么会是/

回答

11

当您指定捕获时,您可以选择按值捕获和按引用捕获。您已选择通过参考捕获。通过引用捕获意味着lambda函数内部的变量引用同一个对象。其含义是,对这个变量的任何改变都将被共享,并且你还需要确保被引用的对象在lambda函数的生命周期中保持不变。

您可能意图通过值来捕获。为此,您可以将捕获规范替换为[=]或变为[x]。后者确保只有x可以访问,而前者可以访问其他变量。

顺便说一句,我会用lock()unlock()明确而要用锁后卫之一建议。有了这个,你的循环的主体看起来是这样的:

vec.push_back(std::thread{[x](){ 
    std::lock_guard<std::mutex> kerberos(m); 
    std::cout << x << "\n"; 
}}); 
+0

我觉得有点愚蠢。我误解了lambda表达式中的&。对于这个过于简单的例子,是的lock_guard是适当的。但是在实际的应用中,函数做了很多事,不需要被锁定。在这种情况下,互斥体只适用于重要部分的锁定和解锁,对吗? – SaulBack

+5

@SaulBack:不,你应该使用'lock_guard'。如果您只想锁定某些代码,请使用带有{}的显式块来封闭已锁定的代码。这里是基本的RAII编码。 –

+1

@SaulBack:特别是在你想使用RAII的复杂代码中,例如'std :: lock_guard '!在这个**微不足道的例子中,我可以看到它不是必需的,但是现在它有点复杂了。如果您需要限制锁定的范围,请使用一个范围,即一对大括号:'{<锁定和代码需要它放在这里>}。 –

6

捕获参数通过而不是按引用,如果你想使一个副本:

vec.push_back(std::thread{[x](){ 
    m.lock(); 
    std::cout << x << std::endl; 
    m.unlock(); 
}}); 

这将在拉姆达对象的创建时间的x值复制(不在线程启动时)。

在我寻找答案时,我偶然发现了这个问题推广C++ 11线程类与lambda一起工作,它建议不要使用会使迭代器无效的容器。为什么会这样/

因为它谈论的是直接使用pthreads库的完全不同的实现。您正在使用std::thread,它被设计为使用C++。

+0

你可以详细说明为什么容器中的ptheads不会工作而std :: thread会吗?这是复制构造函数,移动构造函数,赋值操作符的问题吗? – SaulBack

3

问题是,您通过引用捕获x。因此,当x在每个循环结束时递增时,这反映在线程中。

您希望通过值捕获x,以便lambda只在创建lambda时使用x的值。

for(int x = 0; x < n; ++x) 
{    
    vec.push_back(thread{[x](){ 
     m.lock(); 
     cout << tmp << endl; 
     m.unlock(); 
    }}); 
}