Как уменьшить вероятность того, что производитель получит блокировку, в то время как потребитель не может получить блокировку при использовании std :: condition_variable? - PullRequest
1 голос
/ 17 июня 2020

Как я могу уменьшить вероятность того, что производитель (т. Е. Основной поток в приведенном ниже фрагменте кода) получит блокировку, в то время как потребитель (т. Е. Поток ожидания) не сможет получить блокировку? Было бы лучше, если бы вы могли подсказать мне способ избежать этого. Я не думаю, что использовать std::thread::sleep_for или std::thread::yield - хорошая идея. И я провел несколько тестов и обнаружил, что при использовании std::thread::yield.

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

Если вы запустите фрагмент кода, вы можете увидеть такой результат:

Waiting... 
test 
Notifying falsely... 
Notifying true change... 
Notifying true change... 
Notifying true change... 
Notifying true change... 
Notifying true change... 
Notifying true change...
(**many many such output**)
Notifying true change... 
test 
...finished waiting. i == 1

Вот соответствующий фрагмент кода (проверьте https://godbolt.org/z/9dwDJN, цитата из en.cppreference.com / w / cpp / thread / condition_variable / notify_one):

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{std::cout<<"test"<<std::endl; return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying falsely...\n";
    cv.notify_one(); // waiting thread is notified with i == 0. 
    // cv.wait wakes up, checks i, and goes back to waiting 

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done)
    {
        std::cout << "Notifying true change...\n";
        lk.unlock();
        cv.notify_one(); // waiting thread is notified with i == 1, cv.wait returns 
        //std::this_thread::sleep_for(std::chrono::seconds(1));   // I don't think it is good method.
        //std::this_thread::yield();  //Maybe, this does not work.
        lk.lock();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join();
    t2.join();
}

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Вы можете ждать done полностью без блокировки, если вы сделаете его переменной atomi c. В этом случае для меня это было бы даже больше смысла, чем использование мьютекса. Однако это не меняет всей концепции занятого ожидания , то есть вращения в al oop до тех пор, пока не будет установлено done.

В качестве альтернативы вы можете подождать done устанавливается без блокировки ядра ЦП. Просто используйте ту же концепцию условных переменных. Вы даже можете использовать ту же переменную условия, которая использовалась для синхронизации i. Демонстрация этого подхода находится здесь: https://en.cppreference.com/w/cpp/thread/condition_variable.

Вопрос в том, какое из этих двух решений «лучше». Я, вероятно, предпочел бы первое решение на основе спин, поскольку можно ожидать, что ожидание будет очень коротким (если в системе не будет превышена подписка et c.).

0 голосов
/ 17 июня 2020

Почему вы думаете, что спать - плохая идея? Довольно стандартно использовать std :: this_thread :: sleep_for (std :: chrono :: milliseconds (100)); в потоках while (true), и это также решит вашу проблему.

...