Maison  >  Article  >  Opération et maintenance  >  Quelles sont les différentes méthodes de synchronisation multithread sous Linux ?

Quelles sont les différentes méthodes de synchronisation multithread sous Linux ?

青灯夜游
青灯夜游original
2023-03-10 10:44:352272parcourir

Les méthodes de synchronisation incluent : 1. Mutex est une variable globale spéciale avec deux états : verrouiller et déverrouiller. Le mutex de déverrouillage peut être obtenu par un thread. Lorsque le mutex est détenu par un thread, le mutex sera verrouillé et activé. dans un état de verrouillage.Après cela, seul le thread a le pouvoir d'ouvrir le verrou;2. Le verrou tournant est une boucle infinie, interrogeant constamment 3. Le sémaphore est un compteur utilisé pour contrôler l'accès Le nombre de threads partagés est limité. ressources ; 4. Variables de condition ; 5. Verrouillage en lecture-écriture ; 6. Barrière, qui est un mécanisme de synchronisation permettant aux utilisateurs de coordonner le travail parallèle de plusieurs threads.

Quelles sont les différentes méthodes de synchronisation multithread sous Linux ?

L'environnement d'exploitation de ce tutoriel : système linux7.3, ordinateur Dell G3.

La synchronisation des threads signifie que lorsqu'un thread fonctionne sur une certaine ressource critique, les autres threads ne peuvent pas opérer sur cette ressource jusqu'à ce que le thread termine l'opération, d'autres threads peuvent fonctionner, c'est-à-dire à un rythme coordonné, permettant au thread de fonctionner en fonction. l'exécution dans une séquence prédéterminée. Il existe 6 méthodes de synchronisation des threads : le verrouillage mutex, le verrouillage rotatif, le sémaphore, la variable de condition, le verrouillage en lecture-écriture et la barrière.

Méthode de synchronisation des threads Linux

Ce qui suit est un exemple de thread non sécurisé :

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

int ticket_num=10000000;

void *sell_ticket(void *arg) {
    while(ticket_num>0) {
	ticket_num--;
    }
}

int main() {
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, &sell_ticket, NULL);
    pthread_create(&t2, NULL, &sell_ticket, NULL);
    pthread_create(&t3, NULL, &sell_ticket, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    printf("ticket_num=%d\n", ticket_num);
    return 0;
}

Le résultat de l'exécution est le suivant :

# gcc no_lock_demo.c -o no_lock_demo.out -pthread
# ./no_lock_demo.out 
ticket_num=-2

Le résultat final de l'exécution n'est pas corrigé, il peut être 0, - 1, Si la variable ticket_num représente l'inventaire, alors l'inventaire sera négatif, donc la synchronisation des threads doit être introduite pour garantir la sécurité des threads.

Linux propose une variété de façons de gérer la synchronisation des threads, les plus couramment utilisées sont les verrous mutex, les verrous tournants et les sémaphores.

1. Verrouillage mutex

L'essence d'un verrouillage mutex est une variable globale spéciale, qui a deux états : verrouiller et déverrouiller. Le mutex déverrouillé peut être obtenu par un certain thread. thread, Après avoir été maintenu, le mutex sera verrouillé et deviendra un état de verrouillage.Après cela, seul le thread a le pouvoir d'ouvrir le verrou, et les autres threads qui souhaitent obtenir le mutex seront bloqués jusqu'à ce que le mutex soit déverrouillé.

Type de verrouillage mutex :

  • Verrouillage normal (PTHREAD_MUTEX_NORMAL) : Type de verrouillage mutex par défaut. Lorsqu'un thread verrouille un verrou commun, les threads restants demandant le verrou formeront une file d'attente et obtiendront le verrou en fonction de la priorité après son déverrouillage. Ce type de verrou garantit l'équité dans l'allocation des ressources. Si un thread verrouille un verrou ordinaire qui a été à nouveau verrouillé, cela provoquera un blocage ; le déverrouillage d'un verrou ordinaire qui a été verrouillé par un autre thread, ou le déverrouillage d'un verrou ordinaire qui a été à nouveau déverrouillé, entraînera des conséquences imprévisibles.

  • Verrou de vérification des erreurs (PTHREAD_MUTEX_ERRORCHECK) : si un thread verrouille à nouveau un verrou de vérification des erreurs déjà verrouillé, l'opération de verrouillage renvoie EDEADLK, déverrouillant un verrou de vérification des erreurs qui a été verrouillé par un autre thread ou déverrouillant un Si le le verrou de détection d'erreur déjà déverrouillé est à nouveau déverrouillé, l'opération de déverrouillage revient à EPERM.

  • Verrou imbriqué (PTHREAD_MUTEX_RECURSIVE) : Ce verrou permet à un thread de le verrouiller plusieurs fois avant de le libérer sans blocage ; pour que les autres threads obtiennent ce verrou, le propriétaire du verrou actuel doit effectuer plusieurs opérations de déverrouillage pour déverrouiller une opération imbriquée ; verrou qui a été verrouillé par un autre thread, ou pour déverrouiller à nouveau un verrou imbriqué déjà déverrouillé, l'opération de déverrouillage renvoie EPERM.

  • Verrouillage par défaut (PTHREAD_MUTEX_DEFAULT) : si un thread verrouille à nouveau un verrou par défaut déjà verrouillé, ou déverrouille un verrou par défaut qui a été verrouillé par un autre thread, ou déverrouille un verrou par défaut déverrouillé, cela entraînera des conséquences imprévisibles ; peut être mappé à l’un des trois verrous ci-dessus une fois implémenté.

Méthodes associées :

// 静态方式创建互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

// 动态方式创建互斥锁,其中参数mutexattr用于指定互斥锁的类型,具体类型见上面四种,如果为NULL,就是普通锁。
int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);

int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁,阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁,非阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁

Exemple :

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

int ticket_num=10000000;

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;

void *sell_ticket(void *arg) {
    while(ticket_num>0) {
	pthread_mutex_lock(&mutex);
	if(ticket_num>0) {
	    ticket_num--;
	}
	pthread_mutex_unlock(&mutex);
    }
}

int main() {
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, &sell_ticket, NULL);
    pthread_create(&t2, NULL, &sell_ticket, NULL);
    pthread_create(&t3, NULL, &sell_ticket, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    printf("ticket_num=%d\n", ticket_num);
    return 0;
}

2. Spin lock

Comme son nom l'indique, le spin lock est une boucle infinie, interrogeant en continu lorsqu'un thread n'obtient pas le spin lock. ne pas Il entrera dans un état de veille bloquant comme un verrou mutex, mais interrogera en permanence pour acquérir le verrou. Si le verrou tournant peut être libéré rapidement, les performances seront très élevées si le verrou tournant ne peut pas être libéré pendant une longue période. , même Il existe également une grande quantité de blocage d'E/S, ce qui entraînera une interrogation continue des autres threads qui acquièrent des verrous, ce qui entraînera une utilisation du processeur à 100 %, en particulier le temps CPU.

Méthodes associées :

int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 创建自旋锁

int pthread_spin_lock(pthread_spinlock_t *lock); // 加锁,阻塞
int pthread_spin_trylock(pthread_spinlock_t *lock); // 尝试加锁,非阻塞
int pthread_spin_unlock(pthread_spinlock_t *lock); // 解锁

Exemple :

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

int ticket_num=10000000;

pthread_spinlock_t spinlock;

void *sell_ticket(void *arg) {
    while(ticket_num>0) {
	pthread_spin_lock(&spinlock);
	if(ticket_num>0) {
	    ticket_num--;
	}
	pthread_spin_unlock(&spinlock);
    }
}

int main() {
    pthread_spin_init(&spinlock, 0);
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, &sell_ticket, NULL);
    pthread_create(&t2, NULL, &sell_ticket, NULL);
    pthread_create(&t3, NULL, &sell_ticket, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    printf("ticket_num=%d\n", ticket_num);
    return 0;
}

3 Sémaphore

Le sémaphore est un compteur utilisé pour contrôler le nombre de threads accédant à des ressources partagées limitées.

Méthodes associées :

// 创建信号量
// pshared:一般取0,表示调用进程的信号量。非0表示该信号量可以共享内存的方式,为多个进程所共享(Linux暂不支持)。
// value:信号量的初始值,可以并发访问的线程数。
int sem_init (sem_t* sem, int pshared, unsigned int value);

int sem_wait (sem_t* sem); // 信号量减1,信号量为0时就会阻塞

int sem_trywait (sem_t* sem); // 信号量减1,信号量为0时返回-1,不阻塞

int sem_timedwait (sem_t* sem, const struct timespec* abs_timeout); // 信号量减1,信号量为0时阻塞,直到abs_timeout超时返回-1

int sem_post (sem_t* sem); // 信号量加1

Exemple :

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

int ticket_num=10000000;

sem_t sem;

void *sell_ticket(void *arg) {
    while(ticket_num>0) {
	sem_wait(&sem);
	if(ticket_num>0) {
	    ticket_num--;
	}
	sem_post(&sem);
    }
}

int main() {
    sem_init(&sem, 0, 1); // value=1表示最多1个线程同时访问共享资源,与互斥量等价
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, &sell_ticket, NULL);
    pthread_create(&t2, NULL, &sell_ticket, NULL);
    pthread_create(&t3, NULL, &sell_ticket, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_join(t3, NULL);
    printf("ticket_num=%d\n", ticket_num);
    return 0;
}

4. Variables de condition

Les variables de condition permettent au thread appelant de s'exécuter lorsque des conditions spécifiques sont remplies. Lorsque les conditions ne sont pas remplies, il se bloque et attend d'être réveillé. Il doit être verrouillé avec un mutex À utiliser ensemble.

Les variables conditionnelles sont souvent utilisées dans les modèles de producteur et de consommateur.

Méthodes associées :

pthread_cond_t cond=PTHREAD_COND_INITIALIZER; // 创建条件变量,一个互斥锁可以对应多个条件变量

int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex); // 阻塞等待条件满足,同时释放互斥锁mutex

int pthread_cond_timedwait (pthread_cond_t* cond,
    pthread_mutex_t* mutex,
    const struct timespec* abstime); // 带超时的阻塞等待条件满足,同时释放互斥锁mutex

// 从条件变量cond中唤出一个线程,令其重新获得原先的互斥锁
// 被唤出的线程此刻将从pthread_cond_wait函数中返回,但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
int pthread_cond_signal (pthread_cond_t* cond);

// 从条件变量cond中唤出所有线程
int pthread_cond_broadcast (pthread_cond_t* cond);

Exemples :

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

int max_buffer=10;
int count=0;

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t notempty=PTHREAD_COND_INITIALIZER;
pthread_cond_t notfull=PTHREAD_COND_INITIALIZER;

void *produce(void *args) {
    while(1) {
        pthread_mutex_lock(&mutex);
        while(count == max_buffer) {
            printf("buffer is full, wait...\n");
            pthread_cond_wait(&notfull, &mutex);
        }
        printf("produce ...\n");
        count++;
        sleep(1);
        pthread_cond_signal(&notempty);
        pthread_mutex_unlock(&mutex);
    }

}

void *consumer(void *args) {
    while(1) {
        pthread_mutex_lock(&mutex);
        while(count == 0) {
            printf("buffer is empty, wait...\n");
            pthread_cond_wait(&notempty, &mutex);
        }
        printf("consumer ...\n");
        count--;
        sleep(1);
        pthread_cond_signal(&notfull);
        pthread_mutex_unlock(&mutex);
    }

}

int main() {
    pthread_t t1,t2,t3,t4;
    pthread_create(&t1, NULL, &produce, NULL);
    pthread_create(&t2, NULL, &produce, NULL);

    pthread_create(&t3, NULL, &consumer, NULL);
    pthread_create(&t4, NULL, &consumer, NULL);

    pthread_join(t1, NULL);
    return 0;
}

5、读写锁

读写锁可以有三种状态:读模式下加锁状态,写模式下加锁状态,不加锁状态。一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。读写锁也叫做共享-独占锁,当读写锁以读模式锁住时,它是以共享模式锁住的,当它以写模式锁住时,它是以独占模式锁住的,读读共享,读写互斥。

相关方法:

// 创建读写锁
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 加读锁,阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 加写锁,阻塞
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 释放读锁或者写锁

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 尝试加读锁,非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 尝试加写锁,非阻塞

例子:

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

pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;

void *read(void *arg) {
    while(1) {
        pthread_rwlock_rdlock(&rwlock);
        rintf("read message.\n");
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
void *write(void *arg) {
    while(1) {
        pthread_rwlock_wrlock(&rwlock);
        printf("write message.\n");
        sleep(1);
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}

int main(int argc,char *argv[]) {
    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, &read, NULL);
    pthread_create(&t2, NULL, &read, NULL);

    pthread_create(&t3, NULL, &write, NULL);

    pthread_join(t1, NULL);
    return 0;
}

6、屏障

屏障(barrier)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后所有线程都从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。但屏障对象的概念更广,允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,当所有的线程达到屏障后可以接着工作。

相关方法:

// 创建屏障
int pthread_barrier_init(pthread_barrier_t *barrier,const pthread_barrrierattr_t *attr,unsigned int count)

// 阻塞等待,直到所有线程都到达
int pthread_barrier_wait(pthread_barrier_t *barrier)

例子:

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

pthread_barrier_t barrier;

void *go(void *arg){
    sleep (rand () % 10);
    printf("%lu is arrived.\n", pthread_self());
    pthread_barrier_wait(&barrier);
    printf("%lu go shopping...\n", pthread_self());
}

int main() {
    pthread_barrier_init(&barrier, NULL, 3);

    pthread_t t1,t2,t3;
    pthread_create(&t1, NULL, &go, NULL);
    pthread_create(&t2, NULL, &go, NULL);
    pthread_create(&t3, NULL, &go, NULL);

    pthread_join(t1, NULL);
    return 0;
}

相关推荐:《Linux视频教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn