Порождение потоков в потоке с вызываемым объектом - PullRequest
0 голосов
/ 25 апреля 2018

Я видел эту проблему несколько раз, и кажется, что она возникает как в Windoes (visual studio), так и в Linux (gcc).Вот упрощенная версия:

class noncopyable
{
public:
    noncopyable(int n);
    ~noncopyable();
    noncopyable(const noncopyable&) = delete;
    noncopyable& operator=(const noncopyable&) = delete;
    noncopyable(noncopyable&&);
    int& setvalue();
private:
    int* number;
};

class thread_starter
{
public:
    template<typename callable>
    bool start(callable & o);
};

template<typename callable>
inline bool thread_starter::start(callable & o)
{
    std::thread t(
        [&]() {
        int i = 10;
        while (i-- > 0)
        {
            noncopyable m(i);
            std::thread child(o, std::move(m));
            child.detach();
        }
    });
    return true;
}

class callable
{
public:
    virtual void operator()(noncopyable &m);
};

void callable::operator()(noncopyable & m) { m.setvalue()++; }


int main()
{
    thread_starter ts;
    callable o;
    ts.start(o);
}

Код кажется достаточно корректным, но он не будет компилироваться.

В Visual Studio он даст:

error C2893: Failed to specialize function template 'unknown-type std::invoke(_Callable &&,_Types &&...) noexcept(<expr>)'

в GCC , это даст:

error: no type named ‘type’ in ‘class std::result_of<callable(int)>’....

Я думаю, я знаю, что проблема в некоторой форме механизма копирования или ссылок, но всесинтаксис кажется правильным.

Что мне не хватает?

Я немного изменил пример и извиняюсь за путаницу.Я пытаюсь воссоздать проблему настолько чисто, насколько это возможно, и сам не до конца ее понимаю.

Ответы [ 2 ]

0 голосов
/ 26 апреля 2018

Я сделал аналогичную программу, чтобы выяснить, что происходит. Вот как это выглядит:

class mynoncopy 
{
public:

    mynoncopy(int resource)
        : resource(resource)
    {

    }

    mynoncopy(mynoncopy&& other)
        : resource(other.resource)
    {
        other.resource = 0;
    }

    mynoncopy(const mynoncopy& other) = delete;

    mynoncopy& operator =(const mynoncopy& other) = delete;

public:

    void useResource() {}

private:
    int resource;
};

class mycallablevaluearg
{
public:

    void operator ()(mynoncopy noncopyablething)
    {
        noncopyablething.useResource();
    }
};

class mycallableconstrefarg
{
public:

    void operator ()(const mynoncopy& noncopyablething)
    {
        //noncopyablething.useResource(); // can't do this becuase of const :(
    }
};

class mycallablerefarg
{
public:

    void operator ()(mynoncopy& noncopyablething)
    {
        noncopyablething.useResource();
    }
};

class mycallablervaluerefarg
{
public:

    void operator ()(mynoncopy&& noncopyablething)
    {
        noncopyablething.useResource();
    }
};

class mycallabletemplatearg
{
public:

    template<typename T>
    void operator ()(T&& noncopyablething)
    {
        noncopyablething.useResource();
    }
};

Когда вы запускаете std::thread(callable, std::move(thenoncopyableinstance)), эти две вещи будут происходить внутри с использованием магии шаблона:

  1. Создается кортеж из вашего вызываемого объекта и всех аргументов.
    std::tuple<mycallablerefarg, mynoncopy> thetuple(callable, std::move(thenoncopyableinstance));
    В этом случае вызываемое копирование будет скопировано.

  2. std::invoke() используется для вызова вызываемого объекта, и аргумент передается ему из кортежа с использованием семантики перемещения.
    std::invoke(std::move(std::get<0>(thetuple)), std::move(std::get<1>(thetuple)));

Поскольку используется семантика перемещения, ожидается, что вызываемый объект получит ссылку на rvalue в качестве аргумента (mynoncopy&& в нашем случае). Это ограничивает нас следующими сигнатурами аргументов:

  1. mynoncopy&&
  2. const mynoncopy&
  3. T&& где T - аргумент шаблона
  4. mynoncopy не ссылка (это вызовет конструктор перемещения)

Вот результаты компиляции использования различных типов вызываемых элементов:

mynoncopy testthing(1337);

std::thread t(mycallablerefarg(), std::move(testthing)); // Fails, because it can not match the arguments. This is your case.
std::thread t(mycallablevaluearg(), std::move(testthing)); // OK, because the move semantics will be used to construct it so it will basically work as your solution
std::thread t(mycallableconstrefarg(), std::move(testthing)); // OK, because the argument is const reference
std::thread t(mycallablervaluerefarg(), std::move(testthing)); // OK, because the argument is rvalue reference 
std::thread t(mycallabletemplatearg(), std::move(testthing)); // OK, because template deduction kicks in and gives you noncopyablething&&
std::thread t(std::bind(mycallablerefarg(), std::move(testthing))); // OK, gives you a little bit of call overhead but works. Because bind() does not seem to use move semantics when invoking the callable
std::thread t(std::bind(mycallablevalue(), std::move(testthing))); // Fails, because bind() does not use move semantics when it invokes the callable so it will need to copy the value, which it can't.
0 голосов
/ 25 апреля 2018

Вызов std::thread создает кортеж. Кортежи нельзя инициализировать ссылками. Таким образом, вы должны использовать поддельную ссылку, std::ref(i), чтобы заставить ее скомпилировать и вызвать вызываемые объекты с помощью int-refs int&.

template <typename callable>
bool thread_starter::start(callable &o)
{
    // Nonsense
    std::thread t(
        [&]() {
        int i = 10;
        while (i-- > 0)
        {
            std::thread child(o, std::ref(i));
            child.detach();
        }
    });
    return true;
}

Однако полученный код не имеет смысла. Появившиеся нити конкурируют с циклом while. Цикл уменьшает индекс i, в то время как потоки пытаются увеличить его. Нет никаких гарантий относительно того, когда это произойдет. Увеличение и уменьшение не являются атомарными. Поток может попытаться увеличить индекс после окончания лямбды.

Короче говоря, если вы сделаете его компиляцией, результатом будет неопределенное поведение по ряду причин.

Что вы на самом деле пытаетесь сделать?

...