C++ 螺纹安全和标准:移动

C++ 螺纹安全和标准:移动,c++,lambda,thread-safety,move,C++,Lambda,Thread Safety,Move,前言: 当我输入新代码时,我会不假思索地将我的函数声明为const的pass-by-reference(出于习惯),有时当我意识到这不是我想要做的事情时,我不得不返回并更改它 我正在编写一个工作线程类,该类无限期运行,并(从另一个线程)为处理提供字符串。当我意识到我已经将函数声明为passbyref时,为了线程安全,我返回将其更改为passbyvalue 但是,由于我想挤出尽可能多的速度和效率,我停止自己首先探索的选择。我写了一个小测试程序,发现我对一些关键概念不太清楚。 切中要害:我首先编写


前言: 当我输入新代码时,我会不假思索地将我的函数声明为const的pass-by-reference(出于习惯),有时当我意识到这不是我想要做的事情时,我不得不返回并更改它

我正在编写一个工作线程类,该类无限期运行,并(从另一个线程)为处理提供字符串。当我意识到我已经将函数声明为passbyref时,为了线程安全,我返回将其更改为passbyvalue

但是,由于我想挤出尽可能多的速度和效率,我停止自己首先探索的选择。我写了一个小测试程序,发现我对一些关键概念不太清楚。
切中要害:我首先编写了下面的测试代码,没有注释行:

// 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();
}
#包括
#包括
#包括
#包括
#包括

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

这一定与我用lambda构造了
\u thread
有关:
std::thread_thread([&]{workthread(std::move(str));});//线程不安全(地址被复制)

因此,我将实例化更改为:
std::thread _thread(工作线程,移动(str));//线程安全(内容已移动)
它果然起了作用:

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

Q2:我宣布这是通过引用来购买自己的东西吗

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

创建
\u thread
时,它调用lambda函数,该函数调用
工作线程(std::move(str))
。注意,
std::move
实际上什么都不做;这只是对右值的转换引用。您永远不会从
str
移动,您只是以一种迂回的方式将引用转换为
std::string&
,并分配给它


这也意味着您在
str
上有一个数据竞争,因为您在主线程和
\u线程之间有一个不同步的访问


此代码已从字符串中删除,但:

如果您查看(该列表中的(3)),您将看到它将参数“复制”到函数调用中;它大致要求:

workthread(decay_copy(std::move(str)))
这个
decay\u copy
实际上从字符串中移动,因为它按值返回:

模板
std::decay_t decay_copy(t&&v){返回std::forward(v);}

这就是为什么您将
str
视为从中移动的原因。但是,您的程序实际上依赖于未指定的行为,因为–从
std::string
移动后–字符串处于“有效但未指定的状态”(
std::string
)和)。您不能期望
str
在从中移动后成为空字符串。

您可能总是希望在线程之间复制/移动数据。在线程之间调试悬空引用之类的东西肯定会很痛苦,而且您还需要担心很多所有权问题。如果你担心复制是昂贵的,考虑使用<代码> STD::SyrdYPPTR 或<代码> STD::UnQuyJPPT,但是这也意味着你在STR上有一个数据竞争,因为你在主线程和线程之间有不同步的访问。——是的,这就是我发现的,就像标记线一样。“//不是线程安全的…”回答得很好。这是一个很大的帮助。如果我把你的答案外推到我的第二个问题上,(忽略lambda,只关注构造函数#3),这似乎没有给我买任何东西。我仍然有一个完整的深度副本。你同意吗?@BlairFonville是的,你得到了一个值。它可以被复制(如果你传入左值),或者它可以被移动(如果你通过了右值)。如果你在问题中显示,它将被移动,这非常便宜。哦,我明白了。太好了。谢谢!
std::thread _thread(workthread, move(str));
workthread(decay_copy(std::move(str)))
template <class T>
std::decay_t<T> decay_copy(T&& v) { return std::forward<T>(v); }