Primeros pasos con Embedded Linux 3-4-Thread

hilo

La siguiente tabla proporciona una comparación simple de subprocesos y procesos:

hilo proceso
La unidad más pequeña de programación del sistema operativo La unidad más pequeña de recursos asignados por el sistema operativo.
Comparta directamente recursos como variables globales Comunicarse mediante comunicación entre subprocesos
Una aplicación puede crear múltiples hilos Un programa corresponde a un solo hilo, pero se puede crear otro hilo (una nueva copia del programa) dentro del hilo
Creado usando pthread_create Usa el tenedor para crear

características del hilo

Ventajas sobre los procesos:

  • Sincronización y comunicación entre hilos más simple y eficiente (acceso directo a variables globales para comunicación, sincronización a través de candados; mientras que la sincronización y comunicación entre procesos es más complicada y relativamente menos eficiente);
  • Los recursos y el tiempo consumidos por el subproceso de llamada al sistema son mucho menores que los del proceso;

Desventajas en comparación con los procesos:

  • El bloqueo de un subproceso puede hacer que otros subprocesos de todo el programa mueran juntos, y el bloqueo de un proceso estará protegido por el sistema operativo y no afectará a otros procesos;
  • La incertidumbre en la interacción entre subprocesos hace que los programas sean más difíciles de depurar;

La compilación importa

Al escribir programas multiproceso, debe prestar atención a los siguientes puntos:

  1. Use funciones relacionadas con subprocesos incluyendo el archivo de encabezado pthread.h;
  2. La definición de la macro _REENTRANT debe definirse en tiempo de compilación; por razones específicas, consulte el párrafo citado al final de esta sección;
  3. Utilice la opción -lpthread para vincular la biblioteca multiproceso durante la fase de vinculación;

Por ejemplo, para compilar un programa de subprocesos múltiples compuesto por archivos threads.c, podemos compilar así:

gcc -D_REENTRANT -o threads threads.c -lpthread

La siguiente cita de "Programación de Linux" explica por qué es necesario definir la definición de macro _REENTRANT:

Cuando se diseñaron las rutinas de biblioteca originales de UNIX y POSIX, se asumió que solo había un subproceso de ejecución por proceso. Un ejemplo obvio es errno, que se usa para obtener el mensaje de error después de que falla una llamada de función. En un programa de subprocesos múltiples, de forma predeterminada, solo hay una variable errno compartida por todos los subprocesos. Esta variable se puede cambiar fácilmente mediante una llamada de función en otro subproceso mientras un subproceso está a punto de obtener el código de error anterior. Existe un problema similar con funciones como fputs, que generalmente usan un área global para almacenar en caché los datos de salida.

Para resolver este problema, necesitamos usar lo que se llama rutinas de reentrada . El código reentrante se puede llamar varias veces y seguir funcionando, estas llamadas pueden ser de diferentes subprocesos o alguna forma de llamadas anidadas. Por lo tanto, las partes reentrantes del código generalmente solo usan variables locales, de modo que cada llamada al código obtendrá su propia copia única de los datos.

Al escribir un programa multiproceso, le decimos al compilador que necesitamos la funcionalidad de reentrada definiendo la macro _REENTRANT, que debe definirse antes de cualquier instrucción #include en el programa. Hará 3 cosas por nosotros, y lo hará tan elegantemente que generalmente no necesitamos saber exactamente lo que hace.

  • Redefinirá sus versiones reentrantes seguras de algunas funciones.Los nombres de estas funciones generalmente no cambiarán, pero la cadena _r se agregará después del nombre de la función. Por ejemplo, el nombre de función gethostbyname se convierte en gethostbyname_r.
  • Algunas funciones en stdio.h que se implementaron originalmente como macros se convertirán en funciones de reentrada seguras.
  • La variable errno definida en errno.h ahora será una llamada de función que puede obtener el valor real de errno de forma segura para MT.

Creación y destrucción de hilos

Use la siguiente función para crear un hilo:

/**
 * 以下函数用于创建一个线程,
 * __newthread用于记录线程信息,
 * __attr用于设置线程属性(不需要可以设置为NULL),
 * __start_routine为线程开始执行的函数,__arg为传递给线程的用户参数,
 * 成功返回0,否则返回其他值并设置errno变量
 */
extern int pthread_create (pthread_t *__restrict __newthread,
			   const pthread_attr_t *__restrict __attr,
			   void *(*__start_routine) (void *),
			   void *__restrict __arg) __THROWNL __nonnull ((1, 3));

En comparación con la creación de un hilo, hay varias formas de destruir un hilo:

  • El subproceso mismo llama a la función pthread_exit() para terminar su propia ejecución, o directamente usa return para salir de la función de ejecución del subproceso;
  • Use la función pthread_cancel() en otros hilos para terminar un hilo específico;
  • Si el proceso sale de la función principal mediante la función exit() o el retorno directo, se destruirán todos los subprocesos del proceso;

La siguiente demostración simplemente demuestra la creación y destrucción de subprocesos múltiples:

  1. El subproceso principal imprime el contenido del mensaje inicial, luego crea un nuevo subproceso start_thread y pasa el parámetro del mensaje, y luego bloquea la función pthread_join() para esperar a que se complete el subproceso start_thread;
  2. El subproceso secundario modifica el contenido del mensaje a "¡Ahora, adiós!" y luego sale y devuelve la cadena "Gracias por el tiempo de la CPU";
  3. El subproceso principal imprime el contenido del mensaje modificado por el subproceso y la cadena de retorno del subproceso, y luego el proceso finaliza;
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

void *start_thread(void *arg);

char message[] = "Hello World";

int main(int argc, char *argv[])
{
    
    
    pthread_t thread;
    void *thread_result;

    /* 创建线程 */
    if (pthread_create(&thread, NULL, start_thread, (void *)message) != 0)
    {
    
    
        perror("thread create fialure - ");
        exit(EXIT_FAILURE);
    }

    /* 堵塞在pthread_join函数等待线程执行结束 */
    printf("Waiting for thread to finish...\n");
    if (pthread_join(thread, &thread_result) != 0)
    {
    
    
        perror("thread join failure - ");
        exit(EXIT_FAILURE);
    }

    /* 打印子线程执行结果并退出 */
    printf("Thread joined, it returned: %s\n", (char *)thread_result);
    printf("Message is now %s\n", message);
    exit(EXIT_SUCCESS);
}

void *start_thread(void *arg)
{
    
    
    printf("start_thread is running, Argument was %s\n", (char *)arg);
    sleep(3);
    strcpy(message, "Bye!");
    pthread_exit("Thank you for the CPU time");
}
# 编译指令
gcc -D_REENTRANT -g threads.c -o threads -lpthread

Sincronización de subprocesos y exclusión mutua

Puede ser difícil entender el concepto de sincronización de subprocesos. Dado que el autor tiene experiencia en el uso de RTOS, es fácil de entender. Puede consultar este artículo para establecer el concepto de sincronización y exclusión mutua:

Sistema gráfico: exclusión mutua y sincronización , este artículo es demasiado largo, solo vea qué es sincronización y exclusión mutua.

En los subprocesos de Linux, los semáforos se utilizan para la sincronización entre subprocesos y los mutex se utilizan para la exclusión mutua entre subprocesos.

cantidad de señal

Los semáforos se utilizan para sincronizar hilos. A veces, el subproceso A necesita esperar a que el subproceso B complete ciertas tareas antes de continuar con la ejecución. En este momento, se puede usar un semáforo para sincronizar entre los subprocesos A y B. Para usar funciones y estructuras de datos relacionadas con semáforos de Linux, solo necesita incluir el archivo de encabezado semaphore.h. A continuación, solo se enumeran los prototipos de las funciones de semáforos más utilizadas. Es ineficiente y sin sentido enumerar el uso de funciones en las notas. , deberías hacer un buen uso del comando man.

int sem_init (sem_t *__sem, int __pshared, unsigned int __value); 	/* 创建信号量 */
int sem_wait (sem_t *__sem);										/* 等待信号量 */
int sem_post (sem_t *__sem);										/* 发送信号量 */
int sem_destroy (sem_t *__sem);										/* 销毁信号量 */

exclusión mutua

Al acceder a los recursos compartidos, es necesario utilizar un mutex para resolver el problema de los conflictos causados ​​por la competencia por los recursos compartidos entre varios subprocesos. A continuación se enumeran las interfaces de función mutex comúnmente utilizadas de Linux:

/* Initialize a mutex.  */
extern int pthread_mutex_init (pthread_mutex_t *__mutex,
			       const pthread_mutexattr_t *__mutexattr);

/* Destroy a mutex.  */
extern int pthread_mutex_destroy (pthread_mutex_t *__mutex);

/* Try locking a mutex.  */
extern int pthread_mutex_trylock (pthread_mutex_t *__mutex);

/* Lock a mutex.  */
extern int pthread_mutex_lock (pthread_mutex_t *__mutex);

Supongo que te gusta

Origin blog.csdn.net/lczdk/article/details/124029955
Recomendado
Clasificación