Programación del sistema Linux (7): sincronización de subprocesos

Referencias

1. Concepto de sincronización

  • La llamada sincronización significa empezar al mismo tiempo y de forma coordinada. Diferentes objetos tienen una comprensión ligeramente diferente de la "sincronización"
    • La sincronización de dispositivos se refiere a especificar una referencia de tiempo común entre dos dispositivos.
    • La sincronización de bases de datos se refiere a hacer que el contenido de dos o más bases de datos sea coherente, o parcialmente coherente, según sea necesario.
    • La sincronización de archivos se refiere a mantener consistentes los archivos en dos o más carpetas.
  • Sincronización en programación y comunicación: La palabra "mismo" debe referirse a colaboración, asistencia y cooperación mutua. El propósito es coordinar y ejecutar en un orden predeterminado.

1.1 Sincronización de subprocesos

  • La sincronización de subprocesos significa que cuando un subproceso emite una determinada llamada a función, la llamada no regresa hasta que se obtiene el resultado. Al mismo tiempo, otros subprocesos no pueden llamar a esta función para garantizar la coherencia de los datos.

Insertar descripción de la imagen aquí

  • El fenómeno resultante se denomina "error relacionado con el tiempo" (relacionado con el tiempo). Para evitar esta confusión de datos, los hilos deben sincronizarse
    • El propósito de la "sincronización" es evitar confusión de datos y resolver errores relacionados con el tiempo. De hecho, no solo se necesita sincronización entre subprocesos, sino también mecanismos de sincronización entre procesos , señales, etc. Por lo tanto, todas las situaciones en las que "múltiples flujos de control operan conjuntamente un recurso compartido" requieren sincronización

1.2 Razones de la confusión de datos

  • 1. Compartir recursos (los recursos exclusivos no lo harán)
  • 2. La programación es aleatoria (lo que significa que habrá competencia por el acceso a los datos)
  • 3. Falta del mecanismo de sincronización necesario entre subprocesos.

Entre los tres puntos anteriores, los dos primeros no se pueden cambiar.Para mejorar la eficiencia, se deben compartir recursos al transmitir datos. Mientras se compartan los recursos, inevitablemente habrá competencia, y mientras exista competencia, los datos fácilmente se volverán caóticos. Entonces solo podemos resolverlo desde el punto 3, de modo que múltiples subprocesos sean mutuamente excluyentes al acceder a recursos compartidos.

2. exclusión mutua

  • Linux proporciona un bloqueo mutex (también llamado mutex). Cada hilo intenta bloquearse antes de operar en el recurso. Solo puede operar después de bloquearse exitosamente y se desbloqueará después de la operación . Los recursos todavía se comparten y los hilos aún compiten, pero a través de "bloqueos" el acceso a los recursos se convierte en una operación mutuamente excluyente.

Insertar descripción de la imagen aquí

  • Pero cabe señalar que sólo un hilo puede sujetar el candado al mismo tiempo . Cuando el subproceso A bloquea y accede a una variable global, B intenta bloquearla antes de acceder a ella. Si no puede obtener el bloqueo, B bloquea. El hilo C no se bloquea, sino que accede directamente a la variable global, aún se puede acceder a ella, pero se producirá confusión de datos.
    • Por lo tanto, el bloqueo mutex es esencialmente un "bloqueo de sugerencia" (también conocido como "bloqueo colaborativo") proporcionado por el sistema operativo . Se recomienda utilizar este mecanismo cuando varios subprocesos en el programa acceden a recursos compartidos y no hay un límite obligatorio. .
    • Por lo tanto, incluso con mutex, si un hilo accede a los datos sin seguir las reglas, aún se producirá un caos de datos.

2.1 Funciones principales de la aplicación

#include <pthread.h>

// 返回值都是: 成功返回 0,失败返回错误号
// pthread_mutex_t 类型,其本质是一个结构体
// pthread_mutex_t mutex; 变量 mutex 只有两种取值 1、0

// 初始化一个互斥锁(互斥量) --> 初值可看作 1
// 参 1: 传出参数,调用时应传 &mutex
// restrict 用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成
// 参 2: 互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

// 销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

// 加锁。可理解为将 mutex-- (或 -1),操作后 mutex 的值为 0
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 解锁。可理解为将 mutex++ (或 +1),操作后 mutex 的值为 1
int pthread_mutex_unlock(pthread_mutex_t *mutex);

// 尝试加锁
int pthread_mutex_trylock(pthread_mutex_t *mutex);

2.2 Bloqueo y desbloqueo

2.2.1 bloquear y desbloquear
  • lock intenta bloquearse. Si el bloqueo no tiene éxito, el hilo se bloquea hasta que otros hilos que sostienen el mutex lo desbloquean.
  • desbloquear desbloquea activamente la función y activa todos los subprocesos bloqueados en la cerradura al mismo tiempo. Qué subproceso se activa primero depende de la prioridad y la programación. Predeterminado: bloquear primero, despertar primero
  • Por ejemplo: T1 T2 T3 T4 utiliza un bloqueo mutex. T1 se bloquea con éxito y otros subprocesos se bloquean hasta que se desbloquea T1. Después de desbloquear T1, T2, T3 y T4 se despiertan e intentan bloquearse nuevamente automáticamente. Se puede suponer que el valor inicial exitoso de mutex lock init es 1, la función de bloqueo es cambiar mutex– y la función de desbloqueo es cambiar mutex ++
2.2.2 bloquear y probar
  • lock Si el bloqueo falla, se bloqueará y esperará a que se libere el bloqueo.
  • Si trylock no logra bloquear, devolverá directamente un número de error (como EBUSY) sin bloquear.
  • Bloquee el recurso compartido antes de acceder a él y desbloquéelo inmediatamente después de que se complete el acceso. La "granularidad" de la cerradura debe ser lo más pequeña posible.

2.3 Prueba de paso de bloqueo

  • Crear bloqueo-->Inicializar-->Bloquear-->Acceder a datos compartidos-->Desbloquear-->Destruir bloqueo
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <errno.h>
    #include <pthread.h>
    
    pthread_mutex_t mutex;   // 定义一把全局互斥锁
    
    void *tfn(void *arg) {
          
          
        srand(time(NULL));
    
        while(1) {
          
          
            pthread_mutex_lock(&mutex);    // 加锁,为 0
            printf("hello ");
            sleep(rand() % 3);             // 模拟长时间操作共享资源,导致 cpu 易主,产生与时间有关的错误
            printf("world\n");
            pthread_mutex_unlock(&mutex);  // 解锁,为 1
            sleep(rand() % 3); 
        }   
    
        return NULL;
    }
    
    int main(int argc, char *argv[]) {
          
          
        pthread_t tid;
        srand(time(NULL));
        int ret = pthread_mutex_init(&mutex, NULL);  // 初始化互斥锁,为 1
        if (ret != 0) {
          
          
            fprintf(stderr, "mutex init error: %s\n", strerror(ret));
            exit(1);
        }   
    
        pthread_create(&tid, NULL, tfn, NULL);
        while (1) {
          
          
            pthread_mutex_lock(&mutex);      // 加锁
            printf("HELLO ");
            sleep(rand() % 3); 
            printf("WORLD\n");
            pthread_mutex_unlock(&mutex);    // 解锁
            sleep(rand() % 3); 
        }   
    
        pthread_join(tid, NULL);
    
        pthread_mutex_destroy(&mutex);       // 销毁互斥锁
    
        return 0;
    }
    
    $ gcc pthread_shared.c -o pthread_shared -pthread
    $ ./pthread_shared
    HELLO WORLD
    hello world
    HELLO WORLD
    ...
    

3. Punto muerto

  • Es un fenómeno provocado por el uso inadecuado de las cerraduras.
    • El hilo intenta bloquear el mismo mutex A dos veces ( bloqueando un bloqueo repetidamente )
    • El subproceso 1 posee el candado A y solicita el candado B; el subproceso 2 posee el candado B y solicita el candado A ( cada uno de los dos subprocesos posee un candado y solicita el otro )

Caso

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

#if 1
int var = 1, num = 5;
pthread_mutex_t m_var, m_num;

void *tfn(void *arg) {
    
    
    int i = (int)arg;

    if (i == 1) {
    
    
        pthread_mutex_lock(&m_var);
        var = 22;
        sleep(1);       //给另外一个线程加锁,创造机会.

        pthread_mutex_lock(&m_num);
        num = 66; 

        pthread_mutex_unlock(&m_var);
        pthread_mutex_unlock(&m_num);

        printf("----thread %d finish\n", i);
        pthread_exit(NULL);

    } else if (i == 2) {
    
    
        pthread_mutex_lock(&m_num);
        var = 33;
        sleep(1);

        pthread_mutex_lock(&m_var);
        num = 99; 

        pthread_mutex_unlock(&m_var);
        pthread_mutex_unlock(&m_num);

        printf("----thread %d finish\n", i);
        pthread_exit(NULL);
    }

    return NULL;
}

int main(void) {
    
    
    pthread_t tid1, tid2;
    int ret1, ret2;

    pthread_mutex_init(&m_var, NULL);
    pthread_mutex_init(&m_num, NULL);

    pthread_create(&tid1, NULL, tfn, (void *)1);
    pthread_create(&tid2, NULL, tfn, (void *)2);

    sleep(3);
    printf("var = %d, num = %d\n", var, num);

    ret1 = pthread_mutex_destroy(&m_var);      //释放琐
    ret2 = pthread_mutex_destroy(&m_num);
    if (ret1 == 0 && ret2 == 0) 
        printf("------------destroy mutex finish\n");

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    printf("------------join thread finish\n");

    return 0;
}
#else 

int a = 100;

int main(void) {
    
    
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    pthread_mutex_lock(&mutex);
    a = 777;
    pthread_mutex_lock(&mutex);

    pthread_mutex_unlock(&mutex);

    printf("-----------a = %d\n", a);
    pthread_mutex_destroy(&mutex);

    return 0;
}

#endif
$ gcc deadlock.c -o deadlock -pthread
$ ./deadlock 
var = 33, num = 5
...

4. Bloqueo de lectura y escritura

  • Similar al mutex, pero el bloqueo de lectura y escritura permite un mayor paralelismo. Sus características son: escritura exclusiva, lectura compartida, prioridad de bloqueo de escritura es alta

4.1 Estado de bloqueo de lectura-escritura

  • Sólo hay un bloqueo de lectura y escritura, pero tiene dos estados.
    • Estado bloqueado en modo lectura (bloqueo de lectura)
    • Estado bloqueado en modo de escritura (bloqueo de escritura)

4.2 Características del bloqueo de lectura y escritura

  • Cuando el bloqueo de lectura y escritura es "bloqueo de modo de escritura" , todos los subprocesos que bloquean el bloqueo se bloquearán antes de desbloquearse.
  • Cuando el bloqueo de lectura y escritura está "bloqueado en modo lectura" , si el hilo lo bloquea en modo lectura, tendrá éxito. Si el hilo lo bloquea en modo escritura, se bloqueará.
  • Cuando el bloqueo de lectura y escritura está "bloqueado en modo de lectura", hay subprocesos que intentan bloquearse en modo de escritura y subprocesos que intentan bloquearse en modo de lectura.
    • Los bloqueos de lectura y escritura bloquean solicitudes de bloqueo de modo de lectura posteriores
    • Priorizar el bloqueo del modo de escritura
    • Los bloqueos de lectura y escritura se bloquean en paralelo, y los bloqueos de escritura tienen mayor prioridad.

Los bloqueos de lectura y escritura son muy adecuados para situaciones en las que el número de lecturas de la estructura de datos es mucho mayor que el número de escrituras.

4.3 Funciones principales de la aplicación

#include <pthread.h>

// 返回值 成功返回 0,失败直接返回错误号

// 初始化一把读写锁
// 参 2: attr 表读写锁属性,通常使用默认属性,传 NULL 即可
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

// 销毁一把读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

// 以读方式请求读写锁 (简称:请求读锁)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 非阻塞以读方式请求读写锁 (非阻塞请求读锁)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

// 以写方式请求读写锁 (简称:请求写锁)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 非阻塞以写方式请求读写锁 (非阻塞请求写锁)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

// 解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

4.4 Ejemplo de bloqueo de lectura-escritura

  • 3 subprocesos "escriben" recursos globales de vez en cuando y 5 subprocesos "leen" el mismo recurso global de vez en cuando.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int counter;                          // 全局资源
pthread_rwlock_t rwlock;

void *th_write(void *arg) {
    
    
    int t;
    int i = (int)arg;

    while (1) {
    
    
        t = counter;                  // 保存写之前的值
        usleep(1000);

        pthread_rwlock_wrlock(&rwlock);
        printf("=======write %d: %lu: counter=%d ++counter=%d\n", i, pthread_self(), t, ++counter);
        pthread_rwlock_unlock(&rwlock);

        usleep(9000);                 // 给 r 锁提供机会
    }
    return NULL;
}

void *th_read(void *arg) {
    
    
    int i = (int)arg;

    while (1) {
    
    
        pthread_rwlock_rdlock(&rwlock);
        printf("----------------------------read %d: %lu: %d\n", i, pthread_self(), counter);
        pthread_rwlock_unlock(&rwlock);

        usleep(2000);      // 给写锁提供机会
    }
    return NULL;
}

int main(void) {
    
    
    int i;
    pthread_t tid[8];

    pthread_rwlock_init(&rwlock, NULL);

    for (i = 0; i < 3; i++)
        pthread_create(&tid[i], NULL, th_write, (void *)i);

    for (i = 0; i < 5; i++)
        pthread_create(&tid[i+3], NULL, th_read, (void *)i);

    for (i = 0; i < 8; i++)
        pthread_join(tid[i], NULL);

    pthread_rwlock_destroy(&rwlock);   // 释放读写琐

    return 0;
}
$ gcc rwlock.c -o rwlock -pthread
$ ./rwlock 
----------------------------read 0: 140472231028480: 0
----------------------------read 3: 140472205850368: 0
----------------------------read 2: 140472214243072: 0
----------------------------read 4: 140472197457664: 0
----------------------------read 1: 140472222635776: 0
=======write 0: 140472256206592: counter=0 ++counter=1
=======write 1: 140472247813888: counter=0 ++counter=2
=======write 2: 140472239421184: counter=0 ++counter=3
----------------------------read 2: 140472214243072: 3
----------------------------read 3: 140472205850368: 3
...

5. Variables de condición

  • La variable de condición en sí no es un bloqueo, pero también puede causar el bloqueo del hilo.
  • Generalmente se usa junto con bloqueos mutex para proporcionar un lugar para que se reúnan múltiples subprocesos.

5.1 Funciones principales de la aplicación

#include <pthread.h>

// 返回值 成功返回 0,失败直接返回错误号

// 初始化一个条件变量
// 参 2: attr 表条件变量属性,通常为默认值,传 NULL 即可
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

// 销毁一个条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

// 函数作用(1、2 两步为一个原子操作)
    // 1、阻塞等待条件变量 cond (参 1) 满足
    // 2、释放已掌握的互斥锁 (解锁互斥量),相当于 pthread_mutex_unlock(&mutex);
    // 3、当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

// 限时等待一个条件变量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

// 唤醒至少一个阻塞在条件变量上的线程
int pthread_cond_signal(pthread_cond_t *cond);

// 唤醒全部阻塞在条件变量上的线程
int pthread_cond_broadcast(pthread_cond_t *cond);

Insertar descripción de la imagen aquí

5.2 Modelo de variable de condición productor-consumidor

  • Un caso típico de sincronización de subprocesos es el modelo productor-consumidor , y es un método común implementar este modelo con la ayuda de variables de condición.
    • Supongamos que hay dos subprocesos, uno que simula el comportamiento del productor y otro que simula el comportamiento del consumidor. Dos subprocesos operan un recurso compartido (generalmente llamado agregación) al mismo tiempo: el productor le agrega productos y el consumidor los consume.

Insertar descripción de la imagen aquí

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

void err_thread(int ret, char *str) {
    
    
    if (ret != 0) {
    
    
        fprintf(stderr, "%s:%s\n", str, strerror(ret));
        pthread_exit(NULL);
    }
}

struct msg {
    
    
    int num;
    struct msg *next;
};

struct msg *head;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;       // 定义/初始化一个互斥量
pthread_cond_t has_data = PTHREAD_COND_INITIALIZER;      // 定义/初始化一个条件变量

void *produser(void *arg) {
    
    
    while (1) {
    
    
        struct msg *mp = malloc(sizeof(struct msg));

        mp->num = rand() % 1000 + 1;                        // 模拟生产一个数据`
        printf("--produce %d\n", mp->num);

        pthread_mutex_lock(&mutex);                         // 加锁 互斥量
        mp->next = head;                                    // 写公共区域
        head = mp;
        pthread_mutex_unlock(&mutex);                       // 解锁 互斥量

        pthread_cond_signal(&has_data);                     // 唤醒阻塞在条件变量 has_data上的线程.

        sleep(rand() % 3);
    }

    return NULL;
}

void *consumer(void *arg) {
    
    
    while (1) {
    
    
        struct msg *mp;

        pthread_mutex_lock(&mutex);                         // 加锁 互斥量
        while (head == NULL) {
    
    
            pthread_cond_wait(&has_data, &mutex);           // 阻塞等待条件变量, 解锁
        }                                                   // pthread_cond_wait 返回时, 重新加锁 mutex

        mp = head;
        head = mp->next;

        pthread_mutex_unlock(&mutex);                       // 解锁 互斥量
        printf("---------consumer id: %lu :%d\n", pthread_self(), mp->num);

        free(mp);
        sleep(rand()%3);
    }

    return NULL;
}

int main(int argc, char *argv[]) {
    
    
    int ret;
    pthread_t pid, cid;

    srand(time(NULL));

    ret = pthread_create(&pid, NULL, produser, NULL);           // 生产者
    if (ret != 0) 
        err_thread(ret, "pthread_create produser error");

    ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者
    if (ret != 0) 
        err_thread(ret, "pthread_create consuer error");

    ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者
    if (ret != 0) 
        err_thread(ret, "pthread_create consuer error");

    ret = pthread_create(&cid, NULL, consumer, NULL);           // 消费者
    if (ret != 0) 
        err_thread(ret, "pthread_create consuer error");

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);

    return 0;
}
$ gcc cond.c -o cond -pthread
$ ./cond 
--produce 208
---------consumer id: 140611653785344 :208
--produce 829
---------consumer id: 140611645392640 :829
--produce 191
--produce 625
---------consumer id: 140611662178048 :625
---------consumer id: 140611653785344 :191
--produce 926
---------consumer id: 140611645392640 :926
...

5.3 Ventajas de las variables de condición

  • En comparación con mutex, las variables de condición pueden reducir la competencia
    • Si mutex se usa directamente, además de la competencia entre productores y consumidores por el mutex, los consumidores también deben competir por el mutex, pero si no hay datos en la agregación (lista vinculada), la competencia entre consumidores por el mutex no tiene sentido
    • Con el mecanismo de variable de condición, sólo cuando los productores completen la producción se producirá competencia entre los consumidores, lo que mejora la eficiencia del programa.

6. Cantidad de señal

  • El semáforo equivale a un mutex con un valor inicial de N

    • Dado que la granularidad del bloqueo mutex es relativamente grande, si desea compartir parte de los datos de un objeto entre varios subprocesos, no hay forma de utilizar el bloqueo mutex, solo puede bloquear todo el objeto de datos. Aunque esto logra el propósito de garantizar la exactitud de los datos cuando las operaciones de subprocesos múltiples comparten datos, prácticamente conduce a una disminución en la concurrencia de subprocesos. Los subprocesos cambian de ejecución paralela a ejecución en serie, lo que no es diferente de usar un solo proceso directamente.
  • Semaphore es un método de procesamiento relativamente comprometido que no solo puede garantizar la sincronización y evitar la confusión de datos, sino que también mejora la concurrencia de subprocesos.

6.1 Funciones principales de la aplicación

#include <semaphore.h>

// 返回值 成功返回 0,失败返回-1,同时设置 errno
// 规定信号量 sem 不能 < 0

// 初始化一个信号量
    // 参 1: sem 信号量
    // 参 2: pshared 取 0 用于线程间; 取非 (一般为 1) 用于进程间
    // 参 3: value 指定信号量初值
int sem_init(sem_t *sem, int pshared, unsigned int value);

// 销毁一个信号量
int sem_destroy(sem_t *sem);

// 给信号量加锁 --
int sem_wait(sem_t *sem);
// 尝试对信号量加锁 -- (与 sem_wait 的区别类比 lock 和 trylock)
int sem_trywait(sem_t *sem);
// 限时尝试对信号量加锁 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

// 给信号量解锁 ++
int sem_post(sem_t *sem);
  • Operaciones básicas de semáforo

Insertar descripción de la imagen aquí

6.2 Modelo de semáforo productor-consumidor

Insertar descripción de la imagen aquí

/*信号量实现 生产者 消费者问题*/
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>

#define NUM 5               

int queue[NUM];                                     // 全局数组实现环形队列
sem_t blank_number, product_number;                 // 空格子信号量, 产品信号量

void *producer(void *arg) {
    
    
    int i = 0;

    while (1) {
    
    
        sem_wait(&blank_number);                    // 生产者将空格子数--,为0则阻塞等待
        queue[i] = rand() % 1000 + 1;               // 生产一个产品
        printf("----Produce---%d\n", queue[i]);        
        sem_post(&product_number);                  // 将产品数++

        i = (i+1) % NUM;                            // 借助下标实现环形
        sleep(rand()%1);
    }
}

void *consumer(void *arg) {
    
    
    int i = 0;

    while (1) {
    
    
        sem_wait(&product_number);                  // 消费者将产品数--,为0则阻塞等待
        printf("-Consume---%d\n", queue[i]);
        queue[i] = 0;                               // 消费一个产品 
        sem_post(&blank_number);                    // 消费掉以后,将空格子数++

        i = (i+1) % NUM;
        sleep(rand()%3);
    }
}

int main(int argc, char *argv[]) {
    
    
    pthread_t pid, cid;

    sem_init(&blank_number, 0, NUM);                // 初始化空格子信号量为5, 线程间共享 -- 0
    sem_init(&product_number, 0, 0);                // 产品数为 0

    pthread_create(&pid, NULL, producer, NULL);
    pthread_create(&cid, NULL, consumer, NULL);

    pthread_join(pid, NULL);
    pthread_join(cid, NULL);

    sem_destroy(&blank_number);
    sem_destroy(&product_number);

    return 0;
}
$ gcc sem.c -o sem -pthread
$ ./sem
----Produce---384
-Consume---384
----Produce---916
-Consume---916
...

Supongo que te gusta

Origin blog.csdn.net/qq_42994487/article/details/133437339
Recomendado
Clasificación