Programación multinúcleo: versión básica de pthread

Este es el contenido del curso de programación de múltiples núcleos de la universidad, por lo que aquí hay un breve resumen. Si ya tiene experiencia en programación con varios subprocesos, puede ignorar por completo el contenido de este artículo, es muy rudimentario.

Primero explique, escribo un programa multiproceso en la máquina virtual Linux, incluido el archivo de encabezado

#include <pthread.h> 

Uno, la creación de hilos

La interfaz API de un hilo creado en Linux es pthread_create (), y su definición completa es:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); 

1. Hilo del identificador del hilo: cuando se llama correctamente a un hilo nuevo, el identificador del hilo se devolverá a la persona que llama a través de este parámetro para administrar este hilo.
2. Atributos de subprocesos attr: El segundo parámetro de la interfaz pthread_create () se utiliza para establecer atributos de subprocesos. Este parámetro es opcional.Cuando no necesite modificar los atributos predeterminados del hilo, puede pasarle NULL.
3. Función de entrada start_routine (): Cuando su programa llame a esta interfaz, se generará un hilo y la función de entrada de este hilo es start_routine (). Si el hilo se crea correctamente, esta interfaz devolverá 0.
4. Parámetro de la función de entrada * arg: La función start_routine () tiene un parámetro, que es el último parámetro arg de pthread_create. Este diseño puede preparar algunos datos patentados para el hilo antes de que se cree

  • Objeto de hilo: almacena información de hilo, opaco para los usuarios
  • Tipo de datos del objeto de hilo: pthread_t
  • Crear hilo: pthread_create

En segundo lugar, la función de espera

int pthread_join( pthread_t  thread, void** ret_val_ p);

El primer parámetro de la interfaz pthread_join () es el identificador del hilo recién creado, y el segundo parámetro aceptará el valor de retorno del hilo. La interfaz pthread_join () bloqueará la ejecución del proceso principal hasta que finalice la ejecución del hilo combinado. Dado que el hilo devuelve 0 al sistema después del final, el valor de retorno del hilo obtenido por pthread_join () es naturalmente 0.

Pequeño ejemplo (código copiado, mi interpretación)

#include <stdio.h>  
#include <pthread.h>  
void* thread( void *arg )  
{
    
      
   printf( "This is a thread and arg = %d.\n", *(int*)arg); 
  //将arg强制类型转换为指针,指向变量的值 
    *(int*)arg = 0;  
    return arg;  
}  
int main( int argc, char *argv[] )  
{
    
      
    pthread_t th;  
    int ret;  
    int arg = 10;  
    int *thread_ret = NULL;  
    ret = pthread_create( &th, NULL, thread, &arg ); 
    //成功返回0 
    if( ret != 0 )
    {
    
      
        printf( "Create thread error!\n");  
        return -1;  
    }  
    printf( "This is the main process.\n" );  
    pthread_join( th, (void**)&thread_ret );  
   //二级指针强制类型转换,而后引用
    printf( "thread_ret = %d.\n", *thread_ret );  
    return 0;  
}  

Tres, comunicación y sincronización entre hilos.

Aunque el almacenamiento local de subprocesos puede evitar que los subprocesos accedan a datos compartidos, la mayoría de los datos entre subprocesos siempre se comparten. Cuando se trata de leer y escribir datos compartidos, debe utilizar un mecanismo de sincronización El mecanismo de sincronización de subprocesos proporcionado por Linux incluye principalmente bloqueos mutex y variables de condición.

Sección crítica

Sección crítica:当多个线程访问共享数据时,为保正数据的完整性,将共享数据保护起来

El principio de acceso a la sección crítica:

  1. Como máximo, un hilo puede permanecer en la sección crítica a la vez;
  2. No puede permitir que un hilo permanezca en la sección crítica indefinidamente.

Ocupado esperando

flag = 0;   主线程初始化
………..
y = Compute(my_rank);
while (flag != my_rank);
x = x + y;
flag++;

El orden de ejecución del código de la sección crítica: hilo 0, hilo 1, hilo 2 ...

Mutex

Mutex: límite de que solo un subproceso puede ingresar a la sección crítica a la vez.
Tipo de datos Mutex: pthread_mutex_t

Mutex inicialización
int pthread_mutex_init (pthread_mutex_t mutex_p *,
const pthread_mutexattr_t * attr_p);
liberación mutex
int pthread_mutex_destroy (pthread_mutex_t * mutex_p);
Lock (bloqueo y no-bloqueo)
int pthread_mutex_lock (pthread_mutex_t * mutex_p);
int pthread_mutex_trylock (pthread_mutex_t * mutex_p)
de desbloqueo
int pthread_mutex_unlock (pthread_mutex_t ∗ mutex_p);

Cuando el número de subprocesos es mayor que el número de núcleos, la eficiencia de la espera ocupada disminuye.
Ocupado esperando: enfatiza el orden de acceso a la sección crítica
Mutex: el orden de acceso a la sección crítica es aleatorio

Multiplica varias matrices

voidThread_work(void∗ rank) 
{
    
    
      long my_rank = (long) rank;
      matrix_t my_mat = Allocate_matrix(n);
      Generate_matrix(my_mat);
      pthread_mutex_lock(&mutex);
      Multiply_matrix(product_mat, my_mat);
      pthread_mutex_unlock(&mutex);
      Free_matrix(&my_mat);
      return NULL;
}

Utilice mutex y espera ocupada para implementar obstáculos;

Utilice un contador protegido por un mutex;

Cuando el contador indica que todos los subprocesos han entrado en la sección crítica, los subprocesos pueden salir.

señal

Los semáforos se pueden utilizar en áreas críticas para proteger los recursos compartidos.
Las características del semáforo son las siguientes:

  • El semáforo tiene un número entero no negativo
  • El hilo que quiere acceder al recurso compartido debe adquirir un semáforo, y el semáforo se reduce en 1.
  • Cuando el semáforo es 0, el hilo que intenta acceder al recurso compartido estará en un estado de espera.
  • Si el hilo que sale del recurso compartido libera el semáforo, el semáforo aumenta en 1.

Semaphore se puede considerar como un tipo especial de variable entera unsigned int unsigned, que se puede asignar a 0, 1, 2, 3, etc. Generalmente, solo 0 (correspondiente al mutex bloqueado) / 1 (desbloqueado) Mutex). Para usar un mutex binario como mutex =, el valor del semáforo debe inicializarse a
1, que es el estado desbloqueado. Se llama a la función sem_wait antes de la sección crítica a proteger.Cuando el hilo se ejecuta a la función sem_wait, si el semáforo es 0, el hilo se bloqueará, de lo contrario, entrará en la sección crítica después de restar 1. Después de ejecutar la operación de la sección crítica, llame a sem_post para agregar 1 al valor del semáforo para que otros subprocesos bloqueados en sem_wait puedan continuar ejecutándose.

void* Send_msg(void* rank)
{
    
      
	long my_rank = (long) rank;  
	long dest = (my_rank + 1) % thread_count;  
	char∗  my_msg = malloc(MSG_MAX∗sizeof(char));    			
	sprintf(my_msg, "Hello to %ld from %ld", dest, my_rank);  	  
	messages[dest] = my_msg;  
	sem_post(&semaphores[dest])//信号量为 0,线程就会被阻塞,否则减1 后进去临界区。				       
	sem_wait(&semaphores[my_rank]);  
	printf("Thread %ld > %s n", my_rank, messages[my_rank]);     return NULL;
} 

Sintaxis de diferentes semáforos

int sem_init(sem_t∗ semaphore_p, int shared, unsigned initial_val );
int sem_destroy(sem_t∗ semaphore_p);
int sem_post(sem_t∗ semaphore_p); 
int sem_wait(sem_t∗ semaphore_p);

Nota: El semáforo no es parte de la biblioteca de subprocesos de Pthreads, así que agregue un archivo de encabezado al comienzo del programa que usa el semáforo

#include <semaphore.h>

Barricada

efecto

Sincronice entre subprocesos y asegúrese de que se ejecuten en la misma ubicación. Ningún hilo puede cruzar el obstáculo establecido hasta que todos los hilos hayan llegado aquí.

Utilice la espera ocupada y las exclusiones mutuas para implementar obstáculos

#include <stdio.h>
#include <pthread.h>
#pragma comment(lib, "pthreadVC2.lib")

const int thread = 8;
int count;
pthread_mutex_t pmt;

void* work(void* rank)
{
    
    
    const long long localRank = (long long)rank, dest = (localRank + 1) % thread;
    pthread_mutex_lock(&pmt);   // 进入读写区,上锁,计数器加一,解锁
    printf("Thread %2d reached the barrier.\n", localRank); fflush(stdout);
    count++;
    pthread_mutex_unlock(&pmt);
    while (count < thread);     // 使用忙等待来等所有的线程都达到栅栏
    printf("Thread %2d passed the barrier.\n", localRank); fflush(stdout);
    return ;
}

int main()
{
    
    
    pthread_t pth[thread];
    int i;
    long long list[thread];
    pthread_mutex_init(&pmt, NULL);
    for (i = count = 0; i < thread; i++)
    {
    
    
        list[i] = i;
        pthread_create(&pth[i], NULL, work, (void *)list[i]);
    }
    for (i = 0; i < thread; i++)
        pthread_join(pth[i], NULL);
    pthread_mutex_destroy(&pmt);
    printf("\nfinish.\n");
    getchar();
    return 0;
}

Variable de condición

Una variable de condición permite que un subproceso se detenga hasta que ocurra un evento, cuando se cumple la condición, otro subproceso puede activar el subproceso;

La variable de condición siempre está vinculada al mutex.

Tipo de datos de variable de condición: pthread_cond_t

Inicialización de variable de condición

int pthread_cond_init(pthread_cond_t* cond_var_p, const pthread_condattr_t* attr)

Variable de condición de liberación

int pthread_cond_destroy (pthread_cond_t* cond_var_p)

Desbloquear un hilo bloqueado

int pthread_cond_signal(pthread_cond_t∗ cond_var_p);

Desbloquear todos los hilos bloqueados

int pthread_cond_broadcast(pthread_cond_t∗ cond_var_p);

Bloquear hilos a través de mutex

int pthread_cond_wait(pthread_cond_t∗ cond_var_p,pthread_mutex_t∗ mutex_p);

Utilice variables de condición para implementar obstáculos

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#pragma comment(lib, "pthreadVC2.lib")

const int thread = 8;
int count;
pthread_mutex_t mutex;
pthread_cond_t cond;

void* work(void* rank)
{
    
    
    const long long localRank = (long long)rank, dest = (localRank + 1) % thread;
    printf("Thread %2d reached the barrier.\n", localRank); fflush(stdout);
    pthread_mutex_lock(&mutex);         // 上锁
    count++;
    if (count == thread)                // 最后一个进入的线程
    {
    
    
        count = 0;                      // 计数器清零
        pthread_cond_broadcast(&cond);  // 广播所有线程继续向下执行
    }
    else
        for (; pthread_cond_wait(&cond, &mutex) != 0;);// 等待其他线程
    pthread_mutex_unlock(&mutex);       // 条件变量阻塞解除后会自动将互斥量上锁,需要手工解除

    printf("Thread %2d passed the barrier.\n", localRank); fflush(stdout);
    return ;
}

int main()
{
    
    
    pthread_t pth[thread];
    int i;
    long long list[thread];
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    for (i = count = 0; i < thread; i++)
    {
    
    
        list[i] = i;
        pthread_create(&pth[i], NULL, work, (void *)list[i]);
    }
    for (i = 0; i < thread; i++)
        pthread_join(pth[i], NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    printf("\nfinish.\n");
    getchar();
    return 0;
}

Cuatro, bloqueo de lectura y escritura

El bloqueo de lectura y escritura es un poco como un mutex, pero proporciona dos métodos.

El primero se usa para bloquear la lectura y el segundo se usa para bloquear la escritura;

Muchos subprocesos pueden adquirir un bloqueo de lectura, pero solo un subproceso puede adquirir un bloqueo de escritura.

Si un subproceso adquiere un bloqueo de lectura, otros subprocesos no pueden adquirir un bloqueo de escritura.

初始化
int pthread_rwlock_init(pthread_rwlock_t∗ rwlock_p,
const pthread_rwlockattr_t∗ attr_p );
读加锁
int pthread_rwlock_rdlock(pthread_rwlock_t∗ rwlock_p);
写加锁
int pthread_rwlock_wrlock(pthread_rwlock_t∗ rwlock_p);
解锁
int pthread_rwlock_unlock(pthread_rwlock_t∗ rwlock_p);
销毁
int pthread_rwlock_destroy(pthread_rwlock_t∗ rwlock_p );

Ejemplo detallado

Supongo que te gusta

Origin blog.csdn.net/weixin_45605341/article/details/108368519
Recomendado
Clasificación