【问题标题】:Does move constructor makes any sense for a class that has std::thread's and std::mutex's as its fields?对于具有 std::thread 和 std::mutex 作为其字段的类,移动构造函数是否有意义?
【发布时间】:2017-09-24 14:05:53
【问题描述】:

假设我有一堂课:

class WonnaBeMovedClass
{
public:

    WonnaBeMovedClass(WonnaBeMovedClass&& other);

    void start();

private:

    void updateSharedData();

    std::vector<int> _sharedData;
    std::thread _threadUpdate;
    std::mutex _mutexShaderData;

    //other stuff
};

WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other)
{
    _sharedData = std::move(other._sharedData);
    _threadUpdate = std::move(other._threadUpdate);
    _mutexShaderData = std::move(other._mutexShaderData); //won't compile.
}

void WonnaBeMovedClass::start()
{
    _threadUpdate = std::thread(&updateSharedData, this);
}

void WonnaBeMovedClass::updateSharedData()
{
    std::lock_guard<std::mutex> lockSharedData(_mutexShaderData);

    for (auto& value : _sharedData)
    {
        ++value;
    }
}

它不会编译,因为互斥锁不能被移动。这没有意义。 然后我认为可以通过使用指针而不是实际变量来解决这个问题,并提出以下建议:

class WonnaBeMovedClass
{
public:

    WonnaBeMovedClass(WonnaBeMovedClass&& other);

    void start();

private:

    void updateSharedData();

    std::vector<int> _sharedData;
    std::unique_ptr<std::thread> _threadUpdate //pointer;
    std::unique_ptr<std::mutex> _mutexShaderData //pointer;

    //other stuff
};

WonnaBeMovedClass::WonnaBeMovedClass(WonnaBeMovedClass&& other)
{
    _sharedData = std::move(other._sharedData);
    _threadUpdate = std::move(other._threadUpdate);
    _mutexShaderData = std::move(other._mutexShaderData); //won't compile.
}

void WonnaBeMovedClass::start()
{
    _threadUpdate = std::make_unique<std::thread>(&updateSharedData, this);
}

void WonnaBeMovedClass::updateSharedData()
{
    std::lock_guard<std::mutex> lockSharedData(*_mutexShaderData);

    for (auto& value : _sharedData)
    {
        ++value;
    }
}

所以现在当我:

WonnaBeMovedClass object1;
WonnaBeMovedClass object2;

//do stuff

object1 = std::move(object2);

我实际上移动了互斥锁和线程的地址。 现在更有意义了……还是没有?

线程仍在处理object1的数据,而不是object2,所以它仍然没有任何意义。 我可能已经移动了互斥锁,但线程不知道 object2。或者是吗? 我无法找到答案,所以我向您寻求帮助。

我是不是做错了什么,复制/移动线程和互斥锁只是一个糟糕的设计,我应该重新考虑程序的架构?

编辑:

有一个关于课程的实际目的的问题。它实际上是一个 TCP/IP 客户端(表示为一个类),它持有:

  • 来自服务器的最新数据(几个数据表,类似于 std::vector)。
  • 包含管理线程的方法(更新状态、发送/接收消息)。

更多的是一次可以建立一个连接,所以在代码的某处有一个std::vector&lt;Client&gt; 字段代表所有活动的连接。 连接由配置文件决定。

//read configurations

...

//init clients
for (auto& configuration : _configurations)
{
    Client client(configuration);
    _activeClients.push_back(client); // this is where compiler reminded me that I am unable to move my object (aka WonnaBeMovedClass object).
}}

我已将 _activeClientsstd::vector&lt;Client&gt; 更改为 std::vector&lt;std::unique_ptr&lt;Client&gt;&gt; 并修改了初始化代码以创建指针对象而不是直接创建对象并解决了我的问题,但问题仍然存在,所以我决定在这里发布。

【问题讨论】:

  • 您使用指针的方法也无法编译,还是您的评论是剩余的?
  • 您可以为第一种情况创建一个 PImpl 样式的包装器。第二种方法也可以工作,但是您需要创建 _mutexShaderData
  • 线程是,互斥量没有。那是因为std::thread 是一个线程句柄,而不是实际的线程本身。通常,资源是不可移动的,而资源句柄是可移动的。
  • 一旦您在std::unique_ptr 中拥有互斥锁并且您的所有数据成员都是std::vector&lt;int&gt;std::unique_ptr&lt;...&gt;,您为什么还要定义移动构造函数?一旦你摆脱了不可移动的成员,编译器提供的移动构造函数应该可以正常工作。

标签: c++ multithreading c++11 pointers move


【解决方案1】:

让我们把问题分成两部分。

  1. 移动互斥体。这是无法做到的,因为互斥锁通常是根据必须具有固定地址的 OS 对象来实现的。换句话说,操作系统(或运行时库,就我们的目的而言与操作系统相同)保留了互斥锁的地址。这可以通过在代码中存储(智能)指向互斥体的指针并移动它们来解决。互斥体本身不会移动。线程对象可以移动,所以没有问题。
  2. 移动您自己的数据,而某些活动代码(线程或正在运行的函数或std::function 存储在某处或其他地方)具有您的数据地址并且可以访问它。这实际上与前一种情况非常相似,只是不是操作系统,而是您自己的代码来保存数据。与以前一样,解决方案是不移动数据。而是存储和移动指向数据的(智能)指针。

总结一下,

class WonnaBeMovedClass
{
public:
    WonnaBeMovedClass
        (WonnaBeMovedClass&& other);
    void start();
private:
    struct tdata {
        std::vector<int> _sharedData;
        std::thread _threadUpdate;
        std::mutex _mutexShaderData;
     };
     std::shared_ptr<tdata> data;
     static void updateSharedData(std::shared_ptr<tdata>);
};

void WonnaBeMovedClass::start()
{
    _threadUpdate = std::thread(&updateSharedData, data);
}

【讨论】:

    【解决方案2】:

    现在更有意义了……还是没有?

    不是真的。

    如果std::mutex 被移动,其他线程将不会知道该互斥体内存地址的修改!这会丢弃线程安全。


    但是,std::unique_ptr 的解决方案存在于 Copy or Move Constructor for a class with a member std::mutex (or other non-copyable object)?

    最后,但并非最不重要的一点是,C++14 似乎有一些东西可以发挥作用。阅读更多How should I deal with mutexes in movable types in C++?

    【讨论】:

      【解决方案3】:

      WonnaBeMovedClass 是一个持有线程和互斥体的句柄,因此为它们提供移动语义(但不是复制)并不是一个糟糕的设计

      第二种解决方案看起来不错,但不要忘记为您的互斥体(构造和销毁)进行适当的资源管理。我不太了解该课程的实际用途,因此根据整个解决方案的设计,使用shared_ptr 而不是unique_ptr 可能会更好(以防多个 WonnaBeMovedClass 可以共享同一个互斥体)。

      std::thread本身就是系统线程的句柄,所以不必用指针包裹,资源管理(即OS线程句柄)由标准库自己管理。

      请注意,互斥体实际上是内核对象通常实现为不透明指针,例如在 Windows API 中),因此不应修改或更改 以任何方式通过用户代码。

      【讨论】:

      • 第二种解决方案不好,因为线程存储了一个指向拥有它的对象的指针(请参阅start 函数)。当它被移动时,这个指针没有被调整,仍然指向被移动的对象。
      • 是的,我也想到了这一点。
      猜你喜欢
      • 2011-11-25
      • 1970-01-01
      • 1970-01-01
      • 2018-05-11
      • 1970-01-01
      • 1970-01-01
      • 2013-08-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多