Aprendizaje multiproceso de C++ 09 variables condicionales

1. Variables de condición

Tome el modelo productor-consumidor de la sección anterior como ejemplo, y sus consumidores se ven así:

void XMsgServer::Main()
{
    
    
    while (!is_exit())
    {
    
    
        sleep_for(10ms);
        unique_lock<mutex> lock(mux_);
        if (msgs_.empty())
            continue;
        while (!msgs_.empty())
        {
    
    
            //消息处理业务逻辑
            cout << "recv : " << msgs_.front() << endl;
            msgs_.pop_front();
        }

    }
}

Hay problemas con un modelo productor-consumidor de este tipo:
si el consumidor se mantiene esperando a que el productor produzca, ocupará recursos de la CPU. Si se agrega suspensión después de cada ciclo, el productor puede perder el punto en el tiempo cuando el productor acaba de terminar. producción Por lo tanto, es imposible controlar con precisión el tiempo de suspensión en términos de tiempo, por lo que debe haber una manera de no ocupar la CPU cuando el productor no está produciendo bien, y bloquear allí hasta que se complete la producción, por lo que necesitamos para introducir variables de condición.
Definición de variable de condición:


#include <condition_variable>
condition_variable cv;

La diferencia entre una variable de condición y un semáforo:
el semáforo tiene valor, y el valor del recurso será +1 después de cada operación v, y la cantidad de recursos se verifica antes de cada P, y la variable de
condición no se valora hasta que la cantidad de recursos > = 0. Sí, solo se realiza la función de hacer cola

Dos operaciones de variables de condición:
1. función de espera, wait () se puede dividir en tres operaciones a su vez: liberar el mutex, esperar a que la variable de condición envíe una señal para despertar el hilo actual (en este momento el hilo actual tiene sido bloqueado), cuando se recibe la señal. Cuando se vuelve a adquirir el mutex , tiene dos usos:
(1) espera (unique_lock & lck)

La ejecución del subproceso actual se bloqueará hasta que se reciba la notificación.

(2) esperar (bloqueo_único &lck, predicado pred)

El subproceso actual solo se bloquea cuando pred=false; si pred=true, no se bloquea.

2. La operación de despertar también tiene dos usos:
notificar_uno(): Debido a que solo se despierta el primer subproceso en la cola de espera, no hay contención de bloqueo, por lo que el bloqueo se puede obtener de inmediato. El resto de los subprocesos no se activarán y deberán esperar para llamar a notificar_uno() o notificar_todos() nuevamente.

notificar_todos(): Despertará todos los subprocesos bloqueados en la cola de espera, hay contención de bloqueo y solo un subproceso puede adquirir el bloqueo. Los subprocesos restantes que no han adquirido el bloqueo continuarán intentando adquirir el bloqueo (similar al sondeo) sin volver a bloquearse. Cuando el subproceso que contiene el bloqueo lo libera, uno de esos subprocesos adquiere el bloqueo. El resto intentará entonces adquirir el candado.

Dos, escribir hilo

01. Preparar variable de condición
02. Bloquear con mutex
03. Desbloquear después de completar la producción
04. Variable de condición envía señal

void ThreadWrite()
{
    
    
    for (int i = 0;;i++)
    {
    
    
        stringstream ss;
        ss << "Write msg " << i;
        unique_lock<mutex> lock(mux);
        msgs_.push_back(ss.str());
        lock.unlock();
        cv.notify_one(); //发送信号
        //cv.notify_all();
        this_thread::sleep_for(3s);
    }
}

Generalmente, cv.notify_all() notifica a todos los subprocesos para que salgan cuando finaliza el proceso.

Tres, lee el hilo

01 Obtener el mutex común al hilo que cambia la variable compartida
02 La variable de condición cv espera la notificación de la señal a través de wait(), y permanece bloqueada hasta que recibe la notificación , la cual no ocupará recursos ni demorará.Después
de ingresar wait( ), se desbloqueará primero y luego Para intentar obtener una señal,
dos expresiones de espera():

void wait(unique_lock<mutex>& _Lck) {
    
     // wait for signal
// Nothing to do to comply with LWG‐2135 because std::mutex lock/unlock are
nothrow
_Check_C_return(_Cnd_wait(_Mycnd(), _Lck.mutex()>_Mymtx()));
}
template <class _Predicate>
void wait(unique_lock<mutex>& _Lck, _Predicate _Pred) {
    
     // wait for signal and test
predicate
while (!_Pred())
{
    
     wait(_Lck);
}
}

No se usa la expresión lamda: cuando se recibe la señal, el bloqueo se bloqueará inmediatamente y luego se ejecuta el siguiente código para que sea seguro para subprocesos.Uso de la expresión lamda:
cuando la expresión devuelve verdadero después de recibir la señal, ejecute el siguiente código y devuelve falso Continuar para bloquear (aquí se ejecuta el siguiente código cuando la cola no está vacía y continuar bloqueando cuando está vacía), y luego dejar que otros consumidores juzguen 03 para bloquear e iniciar el consumo después de obtener la
señal

void ThreadRead(int i)
{
    
    
    for (;;)
    {
    
    
        cout << "read msg" << endl;
        unique_lock<mutex> lock(mux);
        //cv.wait(lock);//解锁、阻塞等待信号
        cv.wait(lock, [i] 
            {
    
    
                cout << i << " wait" << endl;
                return !msgs_.empty(); 
            });
        //获取信号后锁定
        while (!msgs_.empty())
        {
    
    
            cout << i << " read " << msgs_.front() << endl;
            msgs_.pop_front();
        }
    }
}

Tres principales

El subproceso productor genera y coloca un dato en la cola cada tres segundos, envía una señal con una variable de condición y luego separa el subproceso productor sin administrar sus recursos de subprocesos para generar tres subprocesos consumidores, y cada subproceso también se separa()
. ,

int main(int argc, char* argv[])
{
    
    
    thread th(ThreadWrite);
    th.detach();
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadRead, i + 1);
        th.detach();
    }
    getchar();
    return 0;
}

inserte la descripción de la imagen aquí

4. Cambie msgserve a la versión variable de condición

Después de que se genera el mensaje, puede responder de inmediato, a diferencia de antes, debe evaluar constantemente si la cola actual no está vacía en el tiempo: el
servidor de mensajes anterior:

void XMsgServer::Main()
{
    
    
    while (!is_exit())
    {
    
    
        sleep_for(10ms);
        unique_lock<mutex> lock(mux_);
        if (msgs_.empty())
            continue;
        while (!msgs_.empty())
        {
    
    
            //消息处理业务逻辑
            cout << "recv : " << msgs_.front() << endl;
            msgs_.pop_front();
        }

    }
}

Versión de la variable de condición: no olvide el puntero this, cuando finaliza el hilo, también debe notificar que es hora de salir.

void XMsgServer::Main()
{
    
    
    while (!is_exit())
    {
    
    
        //sleep_for(10ms);
        unique_lock<mutex> lock(mux_);
        cv_.wait(lock, [this] 
            {
    
    
                cout << "wait cv" << endl;
                return !msgs_.empty(); 
            });
        while (!msgs_.empty())
        {
    
    
            //消息处理业务逻辑
            cout << "recv : " << msgs_.front() << endl;
            msgs_.pop_front();
        }

    }
}

También se debe cambiar el envío de mensajes a los consumidores: primero desbloquear, luego enviar una notificación a un consumidor

void XMsgServer::SendMsg(std::string msg)
{
    
    
    unique_lock<mutex> lock(mux_);
    msgs_.push_back(msg);
    lock.unlock();
    cv_.notify_one();
}

La parada del subproceso del consumidor también debe modificarse. En este momento, todos los subprocesos del consumidor deben estar señalados (aunque solo hay un proceso del consumidor en este ejemplo), si no todos los subprocesos del consumidor están señalados

void XMsgServer::Stop()
{
    
    
    is_exit_ = true;
    cv_.notify_all();
    Wait();
}

resultado:
inserte la descripción de la imagen aquí

Se puede encontrar que finalmente se atasca en el cout de la expresión lamda de la expresión cv Esto se debe a que la cola no está vacía después de ingresar la espera de CV por última vez, y !msgs_.empty(); que bloqueará aquí. Por lo tanto, cuando el subproceso sale is_exit=TRUE, ya no juzgará !msgs_.empty();, sino que devolverá directamente verdadero para ejecutar el código después de la expresión cv hasta que finalice el subproceso:

void XMsgServer::Main()
{
    
    
    while (!is_exit())
    {
    
    
        //sleep_for(10ms);
        unique_lock<mutex> lock(mux_);
        cv_.wait(lock, [this] 
            {
    
    
                cout << "wait cv" << endl;
                if (is_exit())return true;
                return !msgs_.empty(); 
            });
        while (!msgs_.empty())
        {
    
    
            //消息处理业务逻辑
            cout << "recv : " << msgs_.front() << endl;
            msgs_.pop_front();
        }

    }
}

Salido normalmente:
inserte la descripción de la imagen aquí
ps, si no se notifica a todos al detener la llamada, también se bloqueará aquí:
inserte la descripción de la imagen aquí
esto se debe a que el cv.wait() del consumidor siempre está en el segundo paso, es decir, esperando información en un estado bloqueado. antes de continuar ejecutando el siguiente código

Supongo que te gusta

Origin blog.csdn.net/qq_42567607/article/details/126060425
Recomendado
Clasificación