Cómo utilizar correctamente mecanismos de bloqueo y subprocesos múltiples para crear programas confiables

Este artículo se comparte de la comunidad de la nube de Huawei " Garantizar la seguridad de la ejecución concurrente: explorar mecanismos de bloqueo y subprocesos múltiples para crear programas confiables " por Lion Long.

En los sistemas informáticos actuales, la programación multiproceso se ha convertido en un requisito común; sin embargo, también plantea desafíos de ejecución concurrente. Para evitar carreras de datos y otros problemas de concurrencia, el uso correcto de mecanismos de bloqueo adecuados es crucial. Al leer este artículo, los lectores comprenderán la importancia de los mecanismos de bloqueo y subprocesos múltiples en la programación concurrente, y cómo evitar problemas de concurrencia comunes y garantizar la seguridad y confiabilidad del programa. Utilice casos prácticos y ejemplos de código para ilustrar cómo utilizar correctamente mecanismos de bloqueo y subprocesos múltiples para crear programas confiables.

1. El uso de subprocesos múltiples

1.1 Creación de hilos

Prototipo de función:

#include <pthread.h> 

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 
                   void *(*start_routine) (void *), void *arg); 
// Compila y vincula con -pthread.

describir:

La función pthread_create() inicia un nuevo hilo en el proceso de llamada. Un nuevo hilo comienza su ejecución llamando a start_routine(); arg se pasa como único argumento a start_routine().

El nuevo hilo termina de una de las siguientes maneras:

(1) Llama a pthread_exit(), especificando un valor de estado de salida que puede ser utilizado por otro hilo en el mismo proceso que llama a pthrread_join(), es decir, pthrread_join() puede recibir el valor devuelto por pthread_exit().

(2) Regresa desde start_routine(). Esto equivale a llamar a pthread_exit() con el valor proporcionado en la declaración de devolución.

(3) Se cancela mediante pthread_cancel().

(4) Cualquier hilo en el proceso llama a exit(), o el hilo principal regresa de main(). Esto provocará la terminación de todos los subprocesos del proceso.

Introducción de parámetros:

parámetro significado
atributo El parámetro attr apunta a la estructura pthread_attr_t, cuyo contenido se usa para determinar los atributos del nuevo hilo cuando se crea el hilo; use pthread_attr_init() y funciones relacionadas para inicializar la estructura. Si attr está vacío, el hilo se crea con atributos predeterminados.
hilo Antes de regresar, una llamada exitosa a pthread_create() almacena el ID del nuevo hilo en el búfer al que apunta el hilo; este identificador se usa para hacer referencia al hilo en llamadas posteriores a otras funciones de pthreads.
rutina_inicio Función de entrada de hilo
argumento Parámetros de la función de entrada de hilo.

valor de retorno:

En caso de éxito, devuelve 0; en caso de error, devuelve un número de error y el contenido de *thread no está definido.

Numero erroneo:

numero erroneo significado
OTRA VEZ Recursos insuficientes para crear otro hilo.
OTRA VEZ UN Encontré un límite impuesto por el sistema en la cantidad de subprocesos. Hay muchos límites que pueden desencadenar este error: se ha alcanzado el límite de recursos blandos RLIMIT_NPROC [establecido mediante setrlimit()], que limita el número de procesos y subprocesos para una ID de usuario real; el límite de todo el sistema del kernel en el número de Se ha alcanzado el número máximo de procesos y subprocesos, que es /proc/sys/kernel/threads max [ver proc()]; o se ha alcanzado el número máximo de pids /proc/sys/kernel/pid_max [ver proc()].
ELECCIÓN ÚNICA La configuración de la propiedad no es válida.
Superior No hay permiso para establecer la política de programación y los parámetros especificados en attr.

otro:

El nuevo hilo hereda una copia de la máscara de señal [pthread_sigmask()] del hilo creador. El conjunto de señales pendientes del nuevo hilo está vacío [sigpending()]. El nuevo hilo no hereda la pila de señales de respaldo [sigaltstack()] del hilo creador.

El valor inicial del reloj de tiempo de CPU del nuevo hilo es 0 [ver pthread_getcpuclockid()].

Código de muestra:

#incluir <pthread.h> 
#incluir <cadena.h> 
#incluir <stdio.h> 
#incluir <stdlib.h> 
#incluir <unistd.h> 
#incluir <errno.h> 
#incluir <ctype.h> 

# define handle_error_en(en, msg) \ 
        do { errno = en; perror(mensaje); salir(EXIT_FAILURE); } while (0) 

#define handle_error(msg) \ 
        do { perror(msg); salir(EXIT_FAILURE); } while (0) 

struct thread_info { /* Usado como argumento para thread_start() */ 
    pthread_t thread_id; /* ID devuelto por pthread_create() */ 
    int thread_num; /* Hilo definido por la aplicación # */ 
    char *argv_string; /* Desde el argumento de la línea de comandos */ 
}; 

/* Función de inicio de subproceso: muestra la dirección cerca de la parte superior de nuestra pila 
   y devuelve una copia en mayúsculas de argv_string */ 

static void * 
thread_start(void *arg) 
{ 
    struct thread_info *tinfo = arg; 
    char *uargv, *p; 

    printf("Subproceso %d: parte superior de la pila cerca de %p; argv_string=%s\n", 
            tinfo->thread_num, &p, tinfo->argv_string); 

    uargv = strdup(tinfo->argv_string); 
    if (uargv == NULL) 
        handle_error("strdup"); 

    for (p = uargv; *p != '\0'; p++) 
        *p = toupper(*p); 

    devolver uargv; 
} 

int 
main(int argc, char *argv[]) 
{ 
    int s, tnum, opt, num_threads; 
    estructura thread_info *tinfo; 
    pthread_attr_t atributo; 
    int tamaño_pila; 
    vacío *res; 

    /* La opción "-s" especifica un tamaño de pila para nuestros subprocesos */ 

    stack_size = -1; 
    while ((opt = getopt(argc, argv, "s:")) != -1) { 
        switch (opt) { 
        case 's': 
            stack_size = strtoul(optarg, NULL, 0); 
            romper; 

        predeterminado: 
            fprintf(stderr, "Uso: %s [-s tamaño de pila] arg...\n", 
                    argv[0]); 
            salir(EXIT_FAILURE); 
        } 
    } 

    num_threads = argc - optind; 

    /* Inicializa los atributos de creación del hilo */ 

    s = pthread_attr_init(&attr); 
    if (s != 0) 
        handle_error_en(s, "pthread_attr_init"); 

    if (stack_size > 0) { 
        s = pthread_attr_setstacksize(&attr, stack_size); 
        si (s! = 0) 
            handle_error_en(s, "pthread_attr_setstacksize"); 
    } 

    /* Asignar memoria para los argumentos de pthread_create() */ 

    tinfo = calloc(num_threads, sizeof(struct thread_info)); 
    if (tinfo == NULL) 
        handle_error("calloc"); 

    /* Crear un hilo para cada argumento de la línea de comandos */ 

    for (tnum = 0; tnum < num_threads; tnum++) { 
        tinfo[tnum].thread_num = tnum + 1; 
        tinfo[tnum].argv_string = argv[optind + tnum]; 

        /* La llamada pthread_create() almacena el ID del hilo en el 
           elemento correspondiente de tinfo[] */ 

        s = pthread_create(&tinfo[tnum].thread_id, &attr, 
                           &thread_start, &tinfo[tnum]); 
        if (s != 0) 
            handle_error_en(s, "pthread_create"); 
    } 

    /* Destruye el objeto de atributos del hilo, ya que 
       ya no es necesario */ 

    s = pthread_attr_destroy(&attr); 
    if (s != 0) 
        handle_error_en(s, "pthread_attr_destroy"); 

    /* Ahora únete a cada hilo y muestra su valor devuelto */ 

    for (tnum = 0; tnum < num_threads; tnum++) { 
        s = pthread_join(tinfo[tnum].thread_id, &res); 
        if (s != 0) 
            handle_error_en(s, "pthread_join"); 

        printf("Unido al hilo %d; el valor devuelto fue %s\n", 
                tinfo[tnum].thread_num, (char *) res); 
        libre(res); /* Memoria libre asignada por hilo */ 
    } 

    free(tinfo); 
    salir(SALIDA_SUCCESS); 
}

1.2 Terminación del hilo

El nuevo hilo termina de una de las siguientes maneras:

(1) Llama a pthread_exit(), especificando un valor de estado de salida que puede ser utilizado por otro hilo en el mismo proceso que llama a pthrread_join(), es decir, pthrread_join() puede recibir el valor devuelto por pthread_exit().

(2) Regresa desde start_routine(). Esto equivale a llamar a pthread_exit() con el valor proporcionado en la declaración de devolución.

(3) Se cancela mediante pthread_cancel().

(4) Cualquier hilo en el proceso llama a exit(), o el hilo principal regresa de main(). Esto provocará la terminación de todos los subprocesos del proceso.

Prototipo de función pthread_exit():

#include <pthread.h> 

void pthread_exit(void *retval); 

// Compila y vincula con -pthread.

describir:

(1) La función pthread_exit() finaliza el hilo que llama y devuelve un valor a través de retval. Este valor (si el hilo es conectable) puede ser utilizado por otro hilo en el mismo proceso que llama a pthrea_join(), y puede recibirse y devolverse por el valor pthrea_join().

(2) Cualquier controlador de limpieza establecido por pthread_cleanup_push() que aún no se haya extraído se extraerá (en el orden inverso al que se envió) y se ejecutará. Si un subproceso tiene datos específicos del subproceso, se llamará a los destructores correspondientes en un orden no especificado después de que se ejecute el controlador de limpieza.

(3) Cuando finaliza el hilo, los recursos compartidos del proceso (como mutex, variables de condición, semáforos y descriptores de archivos) no se liberarán y no se llamarán las funciones registradas mediante atexit().

(4) Después de que termina el último hilo del proceso, el proceso finaliza llamando a exit() con un estado de salida de cero; por lo tanto, los recursos compartidos del proceso se liberan y se llama a la función registrada usando atexit().

Valor de retorno: esta función no regresa a la persona que llama.

Error: esta función siempre tiene éxito.

Aviso:

(1) Regresar desde la ejecución de la función inicial de cualquier hilo que no sea el hilo principal provocará una llamada implícita a pthread_exit(), utilizando el valor de retorno de la función como el estado de salida del hilo.

(2) Para permitir que otros subprocesos continúen ejecutándose, el subproceso principal debe finalizar llamando a pthread_exit() en lugar de exit().

(3) El valor señalado por retval no debe estar en la pila del hilo que llama, porque el contenido de la pila no está definido después de que termina el hilo.

Prototipo de función pthread_cancel():

#include <pthread.h> 

int pthread_cancel(pthread_t hilo); 

// Compila y vincula con -pthread.

describir:

La función pthread_cancel() envía una solicitud de cancelación al hilo. Si el subproceso de destino responde a una solicitud de cancelación y cuándo depende de dos propiedades controladas por el subproceso: su estado y tipo de cancelabilidad.

El estado cancelable del hilo establecido por pthread_setcancelstate() puede habilitarse (el estado predeterminado para nuevos hilos) o deshabilitarse. Si un hilo tiene la cancelación deshabilitada, las solicitudes de cancelación permanecerán en cola hasta que el hilo habilite la cancelación. Si un hilo tiene la cancelación habilitada, su tipo de cancelabilidad determina cuándo cancelar.

El tipo de cancelación del hilo, determinado por pthread_setcanceltype(), puede ser asíncrono o diferido (el valor predeterminado para hilos nuevos). La cancelabilidad asincrónica significa que un hilo se puede cancelar en cualquier momento (generalmente de inmediato, pero el sistema no lo garantiza). Cancelabilidad retrasada significa que la cancelación se retrasará hasta la próxima vez que el hilo llame a la función que es el punto de cancelación. En pthreads() se proporciona una lista de funciones que son o pueden ser puntos de cancelación.

Al ejecutar una solicitud de cancelación, el hilo realizará los siguientes pasos (en orden):

 
Los pasos anteriores ocurren de forma asincrónica con respecto a la llamada pthread_cancel(); el estado de retorno de pthread_cancel() solo informa a la persona que llama si la solicitud de cancelación se puso en cola con éxito.

Después de que finaliza un hilo cancelado, una conexión a ese hilo usando pthread_join() obtendrá pthrea_canceled como estado de salida del hilo. (Usar una conexión roscada es la única forma de saber que la cancelación se ha completado).

Valor de retorno: cuando tiene éxito, devuelve 0; cuando ocurre un error, devuelve un número de error distinto de cero.

Error: ESRCH, no se puede encontrar el hilo con ID.

1.3 Hilo en espera

Prototipo de función:

#include <pthread.h> 

int pthread_join(pthread_t thread, void **retval); 

// Compila y vincula con -pthread.

describir:

La función pthread_join() espera a que finalice el hilo especificado por el hilo. Si el hilo ha terminado, pthread_join() regresa inmediatamente. El hilo especificado por hilo debe ser conectable.

Si retval no está vacío, pthread_join() copia el estado de salida del hilo de destino (es decir, el valor proporcionado por el hilo de destino a pthrea_exit()) a

La ubicación señalada por retval. Si se cancela el hilo de destino, se establece PTHREAD_CANCELED
en recuperación.

 

Si varios subprocesos intentan unirse al mismo subproceso al mismo tiempo, los resultados no están definidos. Si se cancela el hilo que llama a pthread_join(), el hilo de destino seguirá siendo unible (es decir, no se desconectará).

Valor de retorno: en caso de éxito, devuelve 0; en caso de error, devuelve el número de error.

Numero erroneo:

numero erroneo significado
EDEADLK Se detecta un punto muerto (por ejemplo, dos subprocesos que intentan conectarse entre sí); o el subproceso especifica el subproceso que realiza la llamada.
ELECCIÓN ÚNICA El hilo no es un hilo conectable.
ELECCIÓN ÚNICA Ya hay otro hilo esperando para unirse a este hilo.
ESCRCH No se puede encontrar el hilo con el hilo ID.

1.4 Propiedades del hilo

Prototipo de función:

#include <pthread.h> 

int pthread_attr_init(pthread_attr_t *attr); 
int pthread_attr_destroy(pthread_attr_t *attr); 

// Compila y vincula con -pthread.

describir:

La función pthread_attr_init() inicializa el objeto de atributo de hilo al que apunta attr utilizando el valor de atributo predeterminado. Después de esta llamada, se pueden usar varias funciones relacionadas (enumeradas a continuación) para establecer varias propiedades del objeto, y el objeto luego se puede usar en una o más llamadas pthread_create() que crean subprocesos.

pthread_attr_setaffinity_np(), 
pthread_attr_setdetachstate(), 
pthread_attr_setguardsize(), 
pthread_attr_setinheritsched(), 
pthread_attr_setschedparam(), 
pthread_attr_setschedpolicy(), 
pthread_attr_setscope(), 
pthread_attr_setstack 
(), pthread_attr_setstackaddr() 
, pth read_attr_setstacksize(), 
pthread_create(), 
pthread_getattr_np(), 
pthreads( )

Llamar a pthread_attr_init() en un objeto de atributos de hilo inicializado da como resultado un comportamiento indefinido.

Cuando un objeto de atributo de hilo ya no es necesario, debe destruirse usando la función pthread_attr_destroy().  La destrucción de un objeto de propiedades de subproceso no tiene ningún efecto en los subprocesos creados con el objeto.

Una vez destruido el objeto de atributo del hilo, se puede reinicializar usando pthread_attr_init(). Cualquier otro método que utilice un objeto de propiedad de hilo destruido producirá resultados indefinidos.

valor de retorno:

En caso de éxito, estas funciones devuelven 0; en caso de error, devuelven un número de error distinto de cero.

error:

En Linux, estas funciones siempre tienen éxito (pero las aplicaciones portátiles y preparadas para el futuro deberían manejar posibles errores).

El tipo pthread_attr_t debe considerarse opaco: cualquier acceso al objeto que no sea a través de funciones pthreads no es portátil y produce resultados indefinidos.

Código de muestra:

#define _GNU_SOURCE /* Para obtener la declaración pthread_getattr_np() */ 
#include <pthread.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <errno.h> 

#define handle_error_en(en, msg) \ 
        do { errno = en; perror(mensaje); salir(EXIT_FAILURE); } mientras (0) 

static void 
display_pthread_attr(pthread_attr_t *attr, char *prefix) 
{ 
    int s, i; 
    tamaño_t v; 
    vacío *stkaddr; 
    estructura sched_param sp; 

    s = pthread_attr_getdetachstate(attr, &i); 
    if (s! = 0) 
        handle_error_en(s, "pthread_attr_getdetachstate"); 
    printf("%sDetach estado = %s\n", prefijo, 
            (i == PTHREAD_CREATE_DETACHED)? "PTHREAD_CREATE_DETACHED" : 
            (i == PTHREAD_CREATE_JOINABLE)? "PTHREAD_CREATE_JOINABLE" : 
            "???"); 

    s = pthread_attr_getscope(attr, &i); 
    if (s! = 0) 
        handle_error_en(s, "pthread_attr_getscope"); 
    printf("%sScope = %s\n", prefijo, 
            (i == PTHREAD_SCOPE_SYSTEM)? "PTHREAD_SCOPE_SYSTEM": 
            (i == PTHREAD_SCOPE_PROCESS)? "PTHREAD_SCOPE_PROCESS": 
            "???"); 

    s = pthread_attr_getinheritsched(attr, &i); 
    if (s != 0) 
        handle_error_en(s, "pthread_attr_getinheritsched"); 
    printf("%sHeredar programador = %s\n", prefijo, 
            (i == PTHREAD_INHERIT_SCHED)? "PTHREAD_INHERIT_SCHED": 
            (i == PTHREAD_EXPLICIT_SCHED)? "PTHREAD_EXPLICIT_SCHED" : 
            "???"); 

    s = pthread_attr_getschedpolicy(attr, &i); 
    if (s! = 0) 
        handle_error_en(s, "pthread_attr_getschedpolicy"); 
    printf("%sPolítica de programación = %s\n", prefijo, 
            (i == SCHED_OTHER)? "SCHED_OTHER": 
            (i == SCHED_FIFO)? "SCHED_FIFO": 
            (i == SCHED_RR)? "SCHED_RR": 
            "? ??"); 

    s = pthread_attr_getschedparam(attr, &sp); 
    if (s! = 0) 
        handle_error_en(s, "pthread_attr_getschedparam"); 
    printf("%sPrioridad de programación = %d\n", prefijo, sp.sched_priority); 

    s = pthread_attr_getguardsize(attr, &v); 
    si (s! = 0) 
        handle_error_en(s, "pthread_attr_getguardsize"); 
    printf("%sTamaño de guardia = %d bytes\n", prefijo, v); 

    s = pthread_attr_getstack(attr, &stkaddr, &v); 
    if (s != 0) 
        handle_error_en(s, "pthread_attr_getstack");
    printf("%sDirección de pila = %p\n", prefijo, stkaddr); 
    printf("%sTamaño de pila = 0x%zx bytes\n", prefijo, v); 
} 

vacío estático * 
thread_start(void *arg) 
{ 
    int s; 
    pthread_attr_t gattr; 

    /* pthread_getattr_np() es una extensión GNU no estándar que 
       recupera los atributos del hilo especificado en su 
       primer argumento */ 

    s = pthread_getattr_np(pthread_self(), &gattr); 
    if (s != 0) 
        handle_error_en(s, "pthread_getattr_np"); 

    printf("Atributos del hilo:\n"); 
    display_pthread_attr(&gattr, "\t"); 

    salir(SALIDA_SUCCESS); /* Terminar todos los hilos */ 
} 

int 
main(int argc, char *argv[]) 
{ 
    pthread_t thr; 
    pthread_attr_t atributo; 
    pthread_attr_t *attrp; /* NULL o &attr */ 
    int s; 

    attrp = NULO; 

    /* Si se proporcionó un argumento de línea de comando, utilícelo para establecer el 
       atributo de tamaño de pila y establecer algunos otros atributos de subproceso, 
       y establezca attrp que apunte al objeto de atributos de subproceso */ 

    if (argc > 1) { 
        int stack_size; 
        vacío *sp; 

        attrp = &attr; 

        s = pthread_attr_init(&attr); 
        if (s != 0) 
            handle_error_en(s, "pthread_attr_init"); 

        s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 
        if (s! = 0) 
            handle_error_en(s, "pthread_attr_setdetachstate"); 

        s = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); 
        if (s != 0) 
            handle_error_en(s, "pthread_attr_setinheritsched"); 

        tamaño_pila = strtoul(argv[1], NULL, 0); 

        s = posix_memalign(&sp, sysconf(_SC_PAGESIZE), stack_size); 
        if (s != 0) 
            handle_error_en(s, "posix_memalign"); 

        printf("posix_memalign() asignado en %p\n", sp); 

        s = pthread_attr_setstack(&attr, sp, tamaño_pila); 
        if (s != 0) 
            handle_error_en(s, "pthread_attr_setstack"); 
    } 

    s = pthread_create(&thr, attrp, &thread_start, NULL); 
    if (s != 0) 
        handle_error_en(s, "pthread_create"); 

    if (attrp! = NULL) { 
        s = pthread_attr_destroy(attrp); 
        si (s! = 0) 
            handle_error_en(s, "pthread_attr_destroy"); 
    } 

    pausa(); /* Termina cuando otro hilo llama a exit() */ 
}

2. Sin operaciones atómicas

En múltiples subprocesos, una variable se opera continuamente ¿Qué pasa si no hay operación atómica?

Código de muestra:

#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h> 

#define THREAD_SIZE 10 

// 10 * 100000 
void *func(void *arg) { 

	int *pcount = (int *)arg; 

	int yo = 0; 
	mientras (i++ < 100000) { 
		(*pcount)++; 
		dormir(1); 

	} 
} 

int main(int argc, char **argv) 
{ 
	pthread_t threadid[THREAD_SIZE] = { 0 }; 

	int yo = 0; 
	recuento int = 0; 
	for (i = 0; i < THREAD_SIZE; i++) { 
		pthread_create(&threadid[i], NULL, func, &count); 
	} 

	// 1000w 
	for (i = 0; i < 10; i++) { 
		printf("cuenta = %d\n", cuenta); 
		dormir(1); 
	} 

	devolver 0; 
}

El resultado de la ejecución del código anterior es teóricamente 1000000, pero el resultado final es 994656. Es decir, el resultado de la ejecución sin operaciones atómicas es menor que el valor teórico.

La razón es que el código ensamblador al ejecutar idx++ es:

Mov [idx], %eax 
Inc %eax 
Mov %eax,[idx]

Es decir, el lenguaje C es una declaración, pero cuando realmente se ejecuta, son tres comandos. En ausencia de operaciones atómicas, pueden ocurrir las siguientes situaciones:

La intención original es incrementar dos veces, pero en realidad solo incrementa una vez, por lo que el resultado de la ejecución sin operaciones atómicas es menor que el valor teórico.

3. Bloqueo mutex

Permita que los recursos críticos solo puedan ejecutarse en un hilo.

pthread_mutex_init()

Prototipo de función:

#include <pthread.h> 
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

Función descriptiva:

Inicialización del bloqueo mutex.

La función pthread_mutex_init() crea dinámicamente un bloqueo mutex y el parámetro attr especifica los atributos del nuevo bloqueo mutex. Si el parámetro attr está vacío (NULL), se utiliza el atributo mutex predeterminado y el atributo predeterminado es un mutex rápido.

Los atributos de un mutex se especifican cuando se crea el bloqueo. En la implementación, solo hay un atributo de tipo de bloqueo. Los diferentes tipos de bloqueo se comportan de manera diferente cuando se intenta bloquear un mutex ya bloqueado.

devolver:

Se devuelve cero en caso de éxito; cualquier otro valor devuelto indica un error.

Después del éxito, el mutex se inicializa a un estado desbloqueado.

pthread_mutex_destroy()

Se utiliza para cancelar el registro de un bloqueo mutex, prototipo de función:

#incluye <pthread.h> 
int pthread_mutex_destroy(pthread_mutex_t *mutex)

Destruir un bloqueo mutex significa liberar los recursos que ocupa y requiere que el bloqueo esté actualmente abierto. Dado que los bloqueos mutex no ocupan ningún recurso en Linux, pthread_mutex_destroy() solo verifica el estado del bloqueo (el estado del bloqueo devuelve EBUSY).

pthread_mutex_lock() y pthread_mutex_trylock()

Prototipo de función:

#include <pthread.h> 

int pthread_mutex_lock(pthread_mutex_t *mutex); 
int pthread_mutex_trylock(pthread_mutex_t *mutex);

describir:

El mutex al que hace referencia el mutex se bloquea llamando a pthread_mutex_lock(). Si el mutex ya está bloqueado, el hilo de llamada se bloquea hasta que el mutex esté disponible. Esta operación devuelve el objeto mutex al que hace referencia el mutex en el estado bloqueado, donde el hilo que llama es su propietario.

La función pthread_mutex_trylock() es la misma que pthread_mutex_lock(), excepto que la llamada regresará inmediatamente si el mutex al que hace referencia el mutex está actualmente bloqueado (por cualquier subproceso, incluido el subproceso actual).

Tipos mutuamente excluyentes significado
PTHREAD_MUTEX_NORMAL No se proporciona ninguna detección de interbloqueo. Intentar volver a bloquear el mutex produce un punto muerto. Si un hilo intenta desbloquear un mutex que aún no ha bloqueado o un mutex que ya está desbloqueado, se produce un comportamiento indefinido.
PTHREAD_MUTEX_ERRORCHECK Proporcionar comprobación de errores. Si un hilo intenta volver a bloquear un mutex ya bloqueado, se devuelve un error. Si un hilo intenta desbloquear un mutex que aún no está bloqueado o un mutex que ya está desbloqueado, se devolverá un error.
PTHREAD_MUTEX_RECURSIVE Los mutex conservarán el concepto de recuento de bloqueos. Cuando un subproceso adquiere con éxito un bloqueo mutex por primera vez, el recuento de bloqueos se establece en 1. Cada vez que un subproceso vuelve a bloquear este mutex, el recuento de bloqueos se incrementa en uno. Cada vez que un hilo desbloquea el mutex, el recuento de bloqueos disminuye en uno. Cuando el recuento de bloqueos llega a cero, el mutex queda disponible para que lo adquieran otros subprocesos. Si un hilo intenta desbloquear un mutex que aún no está bloqueado o un mutex que ya está desbloqueado, se devolverá un error.
PTHREAD_MUTEX_DEFAULT Intentar bloquear un mutex de forma recursiva da como resultado un comportamiento indefinido. Si el hilo de llamada no bloquea el mutex, intentar desbloquearlo da como resultado un comportamiento indefinido. Si el mutex está desbloqueado, intentar desbloquearlo da como resultado un comportamiento indefinido.

valor de retorno:

Las funciones pthread_mutex_lock() y pthread_mutex_unlock() devuelven cero si tienen éxito. De lo contrario, se devolverá un número de error para indicar el error.

La función pthread_mutex_trylock() devuelve cero si se adquiere el bloqueo en el mutex al que hace referencia el mutex. De lo contrario, se devolverá un número de error para indicar el error.

Las funciones pthread_mutex_lock() y pthread_mutex_trylock() fallarán si:

código de error significado
ELECCIÓN ÚNICA El mutex se crea utilizando una propiedad de protocolo con el valor PTHREAD_PRIO_PROTECT y el subproceso que realiza la llamada tiene una prioridad superior al límite de prioridad actual del mutex.
OCUPADO El mutex no se puede adquirir porque está bloqueado.
ELECCIÓN ÚNICA El valor especificado por el mutex no se refiere al objeto mutex inicializado.
OTRA VEZ El mutex no se puede adquirir porque se superó el número máximo de bloqueos recursivos para el mutex.
EDEADLK El hilo actual ya posee el mutex.
Superior El hilo actual no es propietario del mutex.

Estas funciones no devuelven el código de error EINTR.

pthread_mutex_unlock ()

Prototipo de función:

#include <pthread.h> 
int pthread_mutex_unlock(pthread_mutex_t *mutex);

describir:

La función pthread_mutex_unlock() libera el objeto mutex al que hace referencia el mutex. La forma en que se libera un mutex depende del atributo de tipo del mutex. Si hay un subproceso bloqueado en el mutex al que hace referencia el mutex cuando se llama a pthread_mutex_unlock(), lo que hace que el mutex esté disponible, la política de programación se utiliza para determinar qué subproceso debe adquirir el mutex. (En el caso de un mutex PTHREAD_MUTEX_RECURSIVE, el mutex pasa a estar disponible cuando el recuento llega a cero y el subproceso que llama ya no bloquea el mutex).

Si se entrega una señal a un subproceso que espera en un mutex, cuando el controlador de señales regresa, el subproceso continuará esperando en el mutex como si no hubiera sido interrumpido.

valor de retorno:

Si tiene éxito, devuelve cero. De lo contrario, se devolverá un número de error para indicar el error.

Código de muestra

#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h> 

#define THREAD_SIZE 10 

#define ADD_MUTEX_LOCK 1 

#if ADD_MUTEX_LOCK 
pthread_mutex_t mutex; 
#endif 

// 10 * 100000 
void *func(void *arg) { 

	int *pcount = (int *)arg; 

	int yo = 0; 
	mientras (i++ < 100000) { 
#if 0 
		(*pcount)++; 
#elif ADD_MUTEX_LOCK 
		pthread_mutex_lock(&mutex); 
		(*cuenta)++; 
		pthread_mutex_unlock(&mutex); 
#endif 
		usleep(1); 

	} 
} 

int main(int argc, char **argv) 
{ 
	pthread_t threadid[THREAD_SIZE] = { 0 }; 

#if ADD_MUTEX_LOCK 
	pthread_mutex_init(&mutex, NULL); 
#endif 

	int i = 0; 
	recuento int = 0; 
	for (i = 0; i < THREAD_SIZE; i++) { 
		pthread_create(&threadid[i], NULL, func, &count); 
	} 

	// 1000w 
	for (i = 0; i < 50; i++) { 
		printf("cuenta = %d\n", cuenta); 
		dormir(1); 
	} 

	devolver 0; 
}

El resultado de la ejecución del código anterior es 1000000. Es decir, el resultado de la ejecución bajo el bloqueo mutex es igual al valor teórico.

5. Bloqueo de giro

La interfaz del bloqueo de giro es similar al mutex.

Prototipo de función:

#include <pthread.h> 
// 1. Destruye el bloqueo de giro 
int pthread_spin_destroy(pthread_spinlock_t *lock); 
// 2. Inicializa el bloqueo de giro 
int pthread_spin_init(pthread_spinlock_t *lock, int attr); 
// 3. Bloqueo de giro en Lock (bloqueo) 
int pthread_spin_lock(pthread_spinlock_t *lock); 
// 4. Bloqueo de bloqueo de giro (sin bloqueo) 
int pthread_spin_trylock(pthread_spinlock_t *lock); 
// 5. Desbloqueo de bloqueo de giro 
int pthread_spin_unlock(pthread_spinlock_t *lock); 
arriba La función regresa 0 si tiene éxito.

Código de muestra

#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h> 

#define THREAD_SIZE 10 

#define ADD_MUTEX_LOCK 0 
#define ADD_SPIN_LOCK 1 

#if ADD_MUTEX_LOCK 
pthread_mutex_t mutex; 
#endif 
#if ADD_SPIN_LOCK 
pthread_spinlock_t spinlock; 
#endif 

// 10 * 100000 
void *func(void *arg) { 

	int *pcount = (int *)arg; 

	int yo = 0; 
	mientras (i++ < 100000) { 
#if 0 
		(*pcount)++; 
#elif ADD_MUTEX_LOCK 
		pthread_mutex_lock(&mutex); 
		(*cuenta)++; 
		pthread_mutex_unlock(&mutex); 
#elif ADD_SPIN_LOCK 
		pthread_spin_lock(&spinlock); 
		(*cuenta)++; 
		pthread_spin_unlock(&spinlock); 
#endif 
		usleep(1); 

	} 
} 

int main(int argc, char **argv) 
{ 
	pthread_t threadid[THREAD_SIZE] = { 0 }; 

#if ADD_MUTEX_LOCK 
	pthread_mutex_init(&mutex, NULL); 
#elif ADD_SPIN_LOCK 
	pthread_spin_init(&spinlock, PTHREAD_PROCESS_SHARED); 
#endif 

	int i = 0; 
	recuento int = 0; 
	for (i = 0; i < THREAD_SIZE; i++) { 
		pthread_create(&threadid[i], NULL, func, &count); 
	} 

	// 1000w 
	for (i = 0; i < 50; i++) { 
		printf("cuenta = %d\n", cuenta); 
		dormir(1); 
	} 

	devolver 0; 
}

El resultado de la ejecución del código anterior es 1000000. Es decir, el resultado de la ejecución bajo el bloqueo de giro es igual al valor teórico.

La diferencia entre bloqueo mutex y bloqueo de giro:

  • Las interfaces de los bloqueos mutex y los bloqueos de giro son similares, pero la implementación subyacente tiene ciertas diferencias.
  • Cuando mutex descubre que el bloqueo ha sido ocupado, cederá los recursos de la CPU y luego esperará a que se active el desbloqueo y tomará el bloqueo.
  • Cuando el giro descubre que el candado ya está ocupado, esperará hasta que lo agarre.

Punto muerto, dos situaciones de punto muerto:

(1) Si dos subprocesos llaman al bloqueo dos veces, al llamar al bloqueo la segunda vez, debido a que el bloqueo ya está ocupado, el hilo colgará y esperará a que otros subprocesos liberen el bloqueo, luego el bloqueo estará ocupado por sí mismo y el hilo Si se suspende nuevamente, el bloqueo no se puede liberar, por lo que estará en un estado de espera suspendido para siempre y entrará en un punto muerto.

(2) Hilo 1 y Hilo 2. El hilo 1 adquiere el bloqueo 1 y el hilo 2 adquiere el bloqueo 2. En este momento, el hilo 1 llama al bloqueo en un intento de adquirir el bloqueo 2. El resultado es que necesita esperar a que el hilo 2 libere el bloqueo 2. En este momento, el hilo 2 también llama al bloqueo en un intento de adquirir el bloqueo 1. El resultado es que el hilo 2 se cuelga esperando a que el hilo 1 libere el bloqueo 1 y entra en un punto muerto.

Evite el punto muerto:

(1) Se debe obtener un bloqueo antes de operar un recurso compartido.

(2) El bloqueo debe liberarse después de completar la operación.

(3) Ocupar la cerradura el menor tiempo posible.

(4) Hay varios candados. Si el orden de adquisición es el candado de cadena abc, el orden de liberación también debe ser abc.

(5) El hilo debe liberar el bloqueo que adquirió cuando regresa con un error.

(6) Al escribir un programa, trate de evitar obtener varios bloqueos al mismo tiempo. Si debe hacer esto, todos los subprocesos adquirirán bloqueos en el mismo orden cuando necesiten varios bloqueos y no se producirá ningún punto muerto.

6. Operaciones atómicas

La operación atómica consiste en utilizar una instrucción para resolver un problema; varios comandos de ejecución se convierten en un solo comando de ejecución, haciéndolos indivisibles.

CAS, el nombre completo es Comparar e intercambiar. Traducido, significa comparar primero y luego asignar, y el orden es inmutable; es decir, comparar primero y luego asignar si los valores son consistentes, y no asignar si son inconsistentes.

Operaciones atómicas comunes:
(1) suma, suma
(2) resta, sub
(3) autoincremento, inc
(4) autodecremento, dec
(5) asignación de comparación, cas

Tenga en cuenta que la ejecución de una declaración en lenguaje C puede tener efectos secundarios, pero las operaciones atómicas no tienen efectos secundarios.

Código de muestra:

#incluye <stdio.h> 
#incluye <pthread.h> 
#incluye <unistd.h> 

#define THREAD_SIZE 10 

#incluye <sys/time.h> 
#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2. tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000) 

// 原子操作
int inc(int *valor,int add) 
{ 
	int old; 
	__asm__ volatile( 
		"bloquear; xaddl %2, %1;" 
		: "=a" (antiguo) 
		: "m" (*valor),"a"(agregar) 
		: "cc","memoria" 
		); 

	volver viejo; 
} 
// 10 * 1000000 
void *func(void *arg) { 

	int *pcount = (int *)arg; 

	int yo = 0; 
	while (i++ < 1000000) { 
	
		inc(pcount, 1); 
		//nosotrosdormir(1); 

	} 
} 

int main(int argc, char **argv) 
{ 
	pthread_t threadid[THREAD_SIZE] = { 0 }; 

	// 统计执行时间
	struct timeval tv_start; 
	gettimeofday(&tv_start, NULL); 

	int yo = 0; 
	recuento int = 0; 
	for (i = 0; i < THREAD_SIZE; i++) { 
		pthread_create(&threadid[i], NULL, func, &count); 
	} 

#if 0 
	// 1000w 
	for (i = 0; i < 50; i++) { 
		printf("cuenta = %d\n", cuenta); 
		dormir(1); 
	} 
#else 
	for (i = 0; i < THREAD_SIZE; i++) { 
		pthread_join(threadid[i], NULL); // 
	} 
#endif 

	struct timeval tv_end; 
	gettimeofday(&tv_end, NULL); 
	int tiempo_usado = TIME_SUB_MS(tv_end, tv_start); 
	printf("tiempo_usado: %d\n", tiempo_usado); 

	devolver 0; 
}

Resumir

Cuando se opera con recursos críticos, comúnmente se utilizan operaciones atómicas y bloqueos.

Los bloqueos incluyen bloqueos mutex, bloqueos de giro, bloqueos de lectura y escritura, etc. Los bloqueos comerciales implementados por otras aplicaciones incluyen bloqueos pesimistas, bloqueos optimistas, etc.

Es fácil llegar a un punto muerto en dos situaciones:

(1) El hilo llama al bloqueo dos veces: la primera vez obtiene el bloqueo y la segunda vez encuentra que el bloqueo está ocupado y entra en espera, pero el bloqueo está ocupado por sí mismo y entra en un punto muerto de espera inalámbrico.

(2) En el caso de múltiples subprocesos y múltiples bloqueos, el subproceso 1 obtiene el bloqueo 1 y luego solicita el bloqueo 2, el subproceso 2 obtiene el bloqueo 2 y luego solicita el bloqueo 1, se espera entre sí y ingresa al bloqueo.

La operación atómica consiste en resolver el problema a través de una instrucción. El CAS encapsulado convierte múltiples comandos de ejecución en un solo comando de ejecución, haciéndolo indivisible.

Haga clic para seguir y conocer las nuevas tecnologías de Huawei Cloud lo antes posible ~

El autor del marco de código abierto NanUI pasó a vender acero y el proyecto fue suspendido. La primera lista gratuita en la App Store de Apple es el software pornográfico TypeScript. Acaba de hacerse popular, ¿por qué los grandes empiezan a abandonarlo? Lista de octubre de TIOBE: Java tiene la mayor caída, C# se acerca Java Rust 1.73.0 lanzado Un hombre fue alentado por su novia AI a asesinar a la Reina de Inglaterra y fue sentenciado a nueve años de prisión Qt 6.6 publicado oficialmente Reuters: RISC-V La tecnología se convierte en la clave de la guerra tecnológica entre China y Estados Unidos. Nuevo campo de batalla RISC-V: no controlado por ninguna empresa o país, Lenovo planea lanzar una PC con Android.
{{o.nombre}}
{{m.nombre}}

Supongo que te gusta

Origin my.oschina.net/u/4526289/blog/10116040
Recomendado
Clasificación