线程安全和std :: move

时间:2018-03-10 02:11:32

标签: c++ lambda thread-safety move

<小时/> 的序: 当我输入新代码时,我将我的函数声明为 pass-by-reference-to-const 而不考虑(出于习惯),有时我必须回过头来改变它这不是我的意思。

我正在编写一个无限期运行的worker-thread类,并提供字符串(来自另一个线程)进行处理。当我意识到我已将该函数声明为 pass-by-ref 时,为了线程安全,我又将其更改为 pass-by-value

但是,既然我想尽可能多地挤出速度和效率,我就停下来先探索各种选择。我写了一个小测试例程 - 发现我对一些关键概念模糊不清。

<小时/> 重点:我首先在没有注释行的情况下编写了下面的测试代码:

// std::thread _thread(workthread, move(str)); // Thread safe (contents are moved)

所以,暂时忽略该行。

#include <iostream>
#include <string>
#include <thread>
#include <chrono>
#include <atomic>

std::atomic<bool> done = false;

void workthread(const std::string &str)
{
    std::string &s = const_cast<std::string &>(str);
    s = "Work Thread"; // test to see if this changes the main thread's string
}

// This just watches for <enter> on the keyboard in order to quit the program.
void quitmonitor()
{
    std::getchar();
    done = true;
}

int main(int argc, char **argv)
{
    std::thread _monitor(quitmonitor);
    std::string str("Main Thread");

    std::thread _thread([&]{workthread(std::move(str));}); // Not thread safe (address is copied)
//  std::thread _thread(workthread, move(str));            // Thread safe (contents are moved)

    const auto minlen(str.length());
    const auto maxlen(minlen ? minlen*2 : 15);
    bool going_up = true;

    while (!done) {

        if (going_up)
            str.push_back('+');
        else
            str.pop_back();

        if (str.length() == minlen)
            going_up = true;
        if (str.length() == maxlen)
            going_up = false;

        std::cout << str << "\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }

    _thread.join();
    _monitor.join();
}

所有main()都会创建一个字符串“主线程”,并将其移动到线程函数void workthread(const std::string &)。然后,线程函数更改左值的数据并返回。主要继续到一个循环,它只是将其本地字符串打印到控制台(有一些额外的眼睛糖果,以便很容易看到屏幕上发生的事情)。这是输出:

function called with lambda

所以,它没有像我预期的那样起作用。我原以为线程实例化会将str“移动”到线程函数(在进程中清空它的数据),而线程对函数的字符串参数的赋值将没有任何影响。但很明显,正如输出所示。

这必须与我使用lambda构造_thread这一事实有关: std::thread _thread([&]{workthread(std::move(str));}); // Not thread safe (address is copied)

然后我将实例化更改为: std::thread _thread(workthread, move(str)); // Thread safe (contents are moved) 它按预期工作: function called as bind

Q1:为什么两个实例lambda vs bind (我猜?)会产生不同的结果?

Q2:我实际上通过将此声明为传递参考来购买任何东西吗?

我应该注意到实际程序非常关键,并且打算在专用服务器上不间断运行多年。我正在努力使软件尽可能低开销,以确保它可以保持同步(使用外部时钟),而不是累积时间错误。

1 个答案:

答案 0 :(得分:3)

std::thread _thread([&]{workthread(std::move(str));});

创建_thread时,它会调用lambda函数,该函数调用workthread(std::move(str))。请注意,std::move实际上并没有做任何事情;它只是一个转换为右值参考。您永远不会从str移出,只是以迂回的方式将引用转换为std::string&并分配给它。

这也意味着您在str上进行了数据竞争,因为主线程与_thread之间存在不同步的访问权限。

此代码从字符串移出:

std::thread _thread(workthread, move(str));

如果您查看std::thread's constructor(该列表中的#3; 3),您会看到它&#34;副本&#34;函数调用的参数;它大致称呼:

workthread(decay_copy(std::move(str)))

这个decay_copy实际上确实从字符串移动,因为它返回值:

template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }

这就是为什么你看到str被移动的原因。但是,您的程序实际上依赖于未指定的行为,因为 - 从std::string移动后 - 字符串保留在&#34;有效但未指定的状态&#34; (std::string&#39; s move constructormove assignment operator)。在将str移出后,您不能指望LaravelLocalization::getCurrentLocale()为空字符串。