Aprendizaje de subprocesos múltiples de C ++ 04 estado de subprocesos múltiples y mutex

1. Descripción del estado del hilo:

Inicialización (Init): el subproceso se está creando: primero solicite un TCB en blanco y complete alguna información del proceso de control y gestión en el TCB; luego, el sistema asigna los recursos necesarios para que se ejecute el proceso; finalmente transfiera el proceso a el estado listo.
Listo (Ready): el subproceso está en la lista de listos, esperando la programación de la CPU.
En ejecución: el subproceso se está ejecutando.
Bloqueado (Blocked): El hilo está bloqueado y suspendido. El estado Bloqueado incluye: pend (bloqueado por bloqueos, eventos, semáforos, etc.), suspend (pendiente activo), delay (bloqueo retrasado), pendtime (esperando tiempos de espera debido a bloqueos, eventos, semáforos, etc.).
Salir (Exit): el subproceso termina de ejecutarse y espera a que el subproceso principal reclame sus recursos de bloque de control.
inserte la descripción de la imagen aquí
Inicialización → Estado listo: el sistema operativo ha asignado recursos para el subproceso y lo ha montado en la cola lista de la CPU.
Listo → Ejecutar: el subproceso se clasifica al principio de la cola de espera y la CPU ha programado el subproceso (como el algoritmo de rotación de intervalos de tiempo, el algoritmo de prioridad de alta tasa de respuesta, la prioridad de trabajo corto, etc.) y se entrega. a la CPU para ejecutar Run→Ready
: el subproceso que se ejecuta en la CPU se utiliza para la programación de la CPU e interrumpe la operación, monta la cola de la cola Ready Ready
→block: X
running→block: cuando el proceso solicita el uso y la asignación de un determinado recurso (como un periférico) o espera un determinado evento Cuando ocurre un evento (como la finalización de una operación de E/S), pasa del estado de ejecución al estado de bloqueo. El proceso solicita al sistema operativo que proporcione servicios en forma de llamadas al sistema. Esta es una forma especial de llamar al proceso del kernel del sistema operativo mediante la ejecución de programas en modo de usuario. Estado de bloqueo → estado listo: cuando el evento que el proceso está
esperando llega, como una operación de E/S. Cuando finaliza la interrupción o la interrupción, el controlador de interrupciones
debe cambiar el estado del proceso correspondiente del estado bloqueado al estado listo.

Es un comportamiento activo que un proceso cambie del estado en ejecución al estado bloqueado, mientras que cambiar del estado bloqueado al estado listo es un comportamiento pasivo que requiere la asistencia de otros procesos relacionados. Por lo tanto, hay un pequeño error en la figura. El estado de bloqueo no se puede cambiar al estado listo, porque el estado de bloqueo ocurre activamente cuando el proceso se está ejecutando: cuando el programa ejecuta llamadas al sistema como read y recv en el bloqueo
I /O, el proceso permanecerá bloqueado hasta que lleguen los datos o alcance el tiempo de espera establecido.
Un proceso puede ingresar explícitamente al bloqueo ejecutando la llamada al sistema de suspensión.
Un proceso en el estado listo no puede ejecutar ningún código que provoque que se bloquee (es decir, no puede ejecutar llamadas al sistema de bloqueo, como lectura/recepción/suspensión), por lo que no se puede convertir a un estado bloqueado.

2. Condiciones de carrera y tramo crítico

Condición de carrera: Múltiples subprocesos leen y escriben datos compartidos al mismo tiempo
Sección crítica: Fragmentos de código para leer y escribir datos compartidos
Evite la estrategia de condición de carrera, proteja la sección crítica y solo un subproceso puede ingresar a la sección crítica al mismo tiempo

void TestThread()
{
    
    
        cout << "==============================" << endl;
        cout << "test 001" << endl;
        cout << "test 002" << endl;
        cout << "test 003" << endl;
        cout << "==============================" << endl;
        this_thread::sleep_for(1000ms);
}
int main(int argc, char* argv[])
{
    
    
    for (int i = 0; i < 10; i++)
    {
    
    
        thread th(TestThread);
        th.detach();
    }
    getchar();
    return 0;
}

El código impreso en la pantalla es la sección crítica. Los 10 subprocesos creados por para usar este código en un intervalo muy rápido, por lo que habrá competencia. Un subproceso imprime la mitad de la línea antes de que esté programado para ejecutar otro subproceso, para que la pantalla esté desordenada

inserte la descripción de la imagen aquí
Así que use el bloqueo mutex:
si desea acceder al área crítica, primero debe realizar la operación de "bloqueo", si el bloqueo es exitoso, luego lea y escriba el área crítica, y libere el bloqueo después de que las operaciones de lectura y escritura hayan terminado. completado; si el "bloqueo" no tiene éxito, entonces el hilo está bloqueado y no ocupa recursos de CPU hasta que el bloqueo sea exitoso.

void TestThread()
{
    
    
    for(;;){
    
    
        //获取锁资源,如果没有则阻塞等待
        mux.lock(); //
        cout << "==============================" << endl;
        cout << "test 001" << endl;
        cout << "test 002" << endl;
        cout << "test 003" << endl;
        cout << "==============================" << endl;
        mux.unlock();
        //this_thread::sleep_for(1000ms);
    }
}

inserte la descripción de la imagen aquí

std::mutex también tiene otra operación: mtx.try_lock(), que literalmente significa: "intentar bloquear". La diferencia con mtx.lock() radica en dos puntos: 01 try_lock()
Si el bloqueo no tiene éxito, el actual thread No bloquea, pero consume recursos de la CPU y continúa esperando para ingresar a la sección crítica, mientras que lock() bloqueará, y otros hilos pueden usar recursos de la CPU en este momento. Motivo: la función de bloqueo está bloqueada porque
se
pasa al llamar a la función WaitForSingleObject El segundo parámetro es INFINITE, lo que significa esperar indefinidamente, por lo que está bloqueado.
La función tryLock no bloquea y regresa inmediatamente después de llamar. Porque el segundo parámetro pasado cuando llama a la función WaitForSingleObject es 0, lo que significa que no espera y regresa inmediatamente.

status_t Mutex::lock()  
{
    
      
    DWORD dwWaitResult;  
    dwWaitResult = WaitForSingleObject((HANDLE) mState, INFINITE);  
    return dwWaitResult != WAIT_OBJECT_0 ? -1 : NO_ERROR;  
}  

void Mutex::unlock()  
{
    
      
    if (!ReleaseMutex((HANDLE) mState))  
        LOG(LOG_WARN, "thread", "WARNING: bad result from unlocking mutex\n");  
}  

status_t Mutex::tryLock()  
{
    
      
    DWORD dwWaitResult;  

    dwWaitResult = WaitForSingleObject((HANDLE) mState, 0);  
    if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_TIMEOUT)  
        LOG(LOG_WARN, "thread", "WARNING: bad result from try-locking mutex\n");  
    return (dwWaitResult == WAIT_OBJECT_0) ? 0 : -1;  
}  

02 : Los tipos de valores de retorno de try_lock() y lock() también son diferentes:
try_lock():

_NODISCARD bool try_lock() {
    
    
        const auto _Res = _Mtx_trylock(_Mymtx());
        switch (_Res) {
    
    
        case _Thrd_success:
            return true;
        case _Thrd_busy:
            return false;
        default:
            _Throw_C_error(_Res);
        }
    }

cerrar con llave():

 void lock() {
    
    
        _Check_C_return(_Mtx_lock(_Mymtx()));
    }

Por lo tanto, se puede usar try_lock() para controlar si el bloqueo es exitoso:

void TestThread()
{
    
    
    for(;;){
    
    
        if (!mux.try_lock())
        {
    
    
            cout << "." << flush;
            this_thread::sleep_for(100ms);
            continue;
        }
        cout << "==============================" << endl;
        cout << "test 001" << endl;
        cout << "test 002" << endl;
        cout << "test 003" << endl;
        cout << "==============================" << endl;
        mux.unlock();
    }
}

try_lock() imprimirá un punto cada vez que falle la solicitud de bloqueo, por ejemplo, se solicita dos veces antes de que llegue con éxito
inserte la descripción de la imagen aquí

Se menciona la diferencia 1 anterior: si lock() no tiene éxito, el subproceso se bloqueará y luego liberará los recursos de CPU ocupados, y otros subprocesos no pueden usar los recursos de CPU; si try_lock() no se bloquea, el subproceso actual no lo hará. bloque, en cambio, ocupa recursos de la CPU y continúa esperando para ingresar a la sección crítica. En este momento, otros subprocesos no pueden usar la CPU, por lo que try_lock() requiere sleep_for (100ms), de lo contrario, los recursos de la CPU se agotarán y el los subprocesos que ocupan la sección crítica no podrán liberar recursos. Puede causar interbloqueo:
elimine this_thread::sleep_for(100ms);
inserte la descripción de la imagen aquí
debido a que estamos equipados con un procesador de alta gama, una pequeña cantidad de subprocesos no causará interbloqueo, pero debido a que try_lock() ocupa la CPU durante mucho tiempo, lo que conduce a la ocupación. Es difícil que los subprocesos en la sección crítica liberen recursos, por lo que try_lock() puede ocupar con éxito los recursos de la CPU muchas veces.

3. El hoyo del bloqueo de exclusión mutua

void ThreadMainMux(int i)
{
    
    
    for (;;)
    {
    
    
        mux.lock();
        cout << i << "[in]" << endl;
        this_thread::sleep_for(1000ms);
        mux.unlock();
        //this_thread::sleep_for(1ms);
    }
}
int main(int argc, char* argv[])
{
    
    
    for (int i = 0; i < 3; i++)
    {
    
    
        thread th(ThreadMainMux, i + 1);
        th.detach();
    }



    getchar();
    for (int i = 0; i < 10; i++)
    {
    
    
        thread th(TestThread);
        th.detach();
    }
    getchar();
    return 0;
}

Teóricamente, después de que entra el subproceso n.° 1, el 23 se bloquea y se coloca en la cola de bloqueo. Después de que se desbloquea el n.° 1, el 23 sale de la cola y entra en estado de ejecución uno por uno. Sin embargo, en realidad, después del n.° 1 desbloquea, irá al bloqueo inmediatamente, y luego el sistema hace un juicio del núcleo: juzgue el bloqueo Si los recursos están ocupados por otros subprocesos, pero el desbloqueo para bloquear es muy rápido, el subproceso aún puede ocupar los recursos del subproceso, y luego el hilo continúa imprimiéndose, bloqueando 23 hilos:
inserte la descripción de la imagen aquí

Así que agregue sleep_for entre desbloquear y bloquear para permitir que el subproceso 1 libere completamente los recursos:
inserte la descripción de la imagen aquí

Supongo que te gusta

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