Как правильно синхронизировать эти два потока? - PullRequest
0 голосов
/ 25 сентября 2019

Я хотел бы правильно синхронизировать разные потоки, но до сих пор у меня была возможность написать неэлегичное решение.Может кто-нибудь любезно указать, как я могу улучшить следующий код?

typedef void (*func)();

void thread(func func1, func func2, int& has_finished, int& id) {
    has_finished--;
    func1();
    has_finished++;
    while (has_finished != 0) std::cout << "thread " << id << " waiting\n";
    std::cout << "thread" << id << "resuming\n";
    func2();
}

int main() {
    int has_finished(0), id_one(0), id_two(1);
    std::thread t1(thread, fun, fun, std::ref(has_finished), std::ref(id_one));
    std::thread t2(thread, fun, fun, std::ref(has_finished), std::ref(id_two));
    t1.join();
    t2.join();
};

Суть программы описывается функцией thread.Функция выполняется двумя std::thread с.Функция принимает две длительно выполняемые функции func1 и func2 и две ссылки на целые числа в качестве аргументов. Потоки должны вызывать func2 только после выхода из всех потоков func1.Аргумент has_finished используется для координации различных потоков: при входе в функцию has_arguments равен нулю.Затем каждый std::thread уменьшает значение и вызывает длительную функцию func1.Оставив func1, has_finished снова увеличивается.Пока это значение не равно исходному значению нуля, поток ожидает.Затем каждый поток работает на func2.Основная функция показана в конце.

Как лучше координировать два потока?Я думал об использовании std::mutex и std::condition_variable, но не мог понять, как их правильно использовать?У кого-нибудь есть идеи, как я могу улучшить программу?

Ответы [ 2 ]

2 голосов
/ 25 сентября 2019

Не пиши это сам.Этот вид синхронизации известен как «защелка» (или, в более общем смысле, «барьер», и он доступен через различные библиотеки и через C ++ Concurrency TS. (Он также может превратиться в C ++ 20 в некоторой форме.)

Например, используя версию из Boost :

#include <iostream>
#include <thread>

#include <boost/thread/latch.hpp>

void f(boost::latch& c) {
    std::cout << "Doing work in round 1\n";
    c.count_down_and_wait();
    std::cout << "Doing work in round 2\n";
}

int main() {
    boost::latch c(2);

    std::thread t1(f, std::ref(c)), t2(f, std::ref(c));
    t1.join();
    t2.join();
}
1 голос
/ 25 сентября 2019

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

Вот класс Gate, демонстрирующий, как использовать условную переменную для реализации шлюза, который ждет, пока некоторое количество потоков достигнет его, прежде чем продолжить:

#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <sstream>
#include <utility>
#include <cassert>

struct Gate {
 public:
    Gate(unsigned int count = 2) : count_(count) { }  // How many threads need to reach the gate before it unlocks
    Gate(Gate const &) = delete;
    void operator =(Gate const &) = delete;

    void wait_for_gate();

 private:
    int count_;
    ::std::mutex count_mutex_;
    ::std::condition_variable count_gate_;
};

void Gate::wait_for_gate()
{
    ::std::unique_lock<::std::mutex> guard(count_mutex_);
    assert(count > 0); // Count being 0 here indicates an irrecoverable programming error.
    --count_;
    count_gate_.wait(guard, [this](){ return this-> count_ <= 0; });
    guard.unlock();
    count_gate_.notify_all();
}

void f1()
{
    ::std::ostringstream msg;
    msg << "In f1 with thread " << ::std::this_thread::get_id() << '\n';
    ::std::cout << msg.str();
}

void f2()
{
    ::std::ostringstream msg;
    msg << "In f2 with thread " << ::std::this_thread::get_id() << '\n';
    ::std::cout << msg.str();
}

void thread_func(Gate &gate)
{
    f1();
    gate.wait_for_gate();
    f2();
}

int main()
{
    Gate gate;
    ::std::thread t1{thread_func, ::std::ref(gate)};
    ::std::thread t2{thread_func, ::std::ref(gate)};
    t1.join();
    t2.join();
}

Надеюсь, структура этого кода будет достаточно похожа на ваш код, чтобы вы могли понять, что здесь происходит.При чтении кода кажется, что вы ищете все потоки для выполнения func1, а затем func2.Вы не хотите, чтобы func2 работал, пока какой-либо поток выполняет func1.

. Это можно рассматривать как ворота, в которых все потоки ожидают прибытия в местоположение 'Finished Func1', прежде чем перейти кзапустите func2.

Я тестировал этот код на собственной локальной версии проводника компилятора.

Основным недостатком защелки в другом ответе является то, что он еще не является стандартным C ++.Мой класс Gate представляет собой простую реализацию класса защелки, упомянутого в другом ответе, и является стандартным C ++.

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

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

Мьютекс защищает переменную общего счета.Всякий раз, когда у вас есть общее значение, вы должны защищать его мьютексом, чтобы ни один поток не мог увидеть это значение в несогласованном состоянии.Условие состоит в том, что потоки могут ожидать, пока это число достигнет 0, что указывает на то, что все потоки уменьшили значение переменной count.

...