6 façons : 1. Verrouillage Mutex, qui est essentiellement une variable globale spéciale avec deux états de verrouillage et de déverrouillage ; 2. Verrouillage rotatif, qui est une boucle infinie et continue d'interroger ; 3. Sémaphore, utilisé pour contrôler le nombre de threads ; accéder à des ressources partagées limitées ; 4. Variables de condition, qui permettent au thread appelant de s'exécuter lorsque certaines conditions sont remplies, et de bloquer et d'attendre d'être réveillé lorsque les conditions ne sont pas remplies 5. Verrouillage en lecture-écriture, il ne peut y avoir qu'un seul thread ; à la fois Peut occuper des verrous de lecture-écriture en mode écriture ; 6. La barrière est un mécanisme de synchronisation permettant aux utilisateurs de coordonner le travail parallèle de plusieurs threads.
L'environnement d'exploitation de ce tutoriel : système linux7.3, ordinateur Dell G3.
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 d'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 d'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 plusieurs 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.
L'essence d'un verrouillage mutex est une variable globale spéciale avec deux états : verrouiller et déverrouiller. Le mutex déverrouillé peut être obtenu par un thread Lorsque le mutex est détenu par un thread. Ensuite, le verrouillage mutex. sera verrouillé et deviendra l'état de verrouillage.Après cela, seul le thread a le pouvoir d'ouvrir le verrou, et les autres threads qui souhaitent obtenir le verrou mutex seront bloqués jusqu'à ce que le verrou 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 ; 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; }
Comme son nom l'indique, le spin lock est une boucle infinie, interrogeant en continu lorsqu'un thread n'obtient pas le spin lock, il ne se comportera pas. comme Le verrou mutex entre également dans l'état de veille bloquant, mais interroge 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, cela peut même être le cas. Il y a quelque chose à l'intérieur. Il y a 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 fera que l'utilisation du processeur atteint 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; }
Un sémaphore est un compteur utilisé pour contrôler le nombre de threads accédant à une ressource partagée limitée.
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; }
Les variables conditionnelles 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é. être utilisé avec un verrou mutex .
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);
Exemple :
#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(¬full, &mutex); } printf("produce ...\n"); count++; sleep(1); pthread_cond_signal(¬empty); 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(¬empty, &mutex); } printf("consumer ...\n"); count--; sleep(1); pthread_cond_signal(¬full); 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; }
Le verrouillage en lecture-écriture peut avoir trois états : état verrouillé en mode lecture, état verrouillé en mode écriture et état déverrouillé. Un seul thread peut détenir un verrou en lecture-écriture en mode écriture à la fois, mais plusieurs threads peuvent détenir un verrou en lecture-écriture en mode lecture en même temps. Un verrou en lecture-écriture est également appelé verrou partagé-exclusif. Lorsqu'un verrou en lecture-écriture est verrouillé en mode lecture, il est verrouillé en mode partagé. Lorsqu'il est verrouillé en mode écriture, il est verrouillé en mode lecture. verrouillage en écriture Partagé, lecture et écriture mutuellement exclusives.
Un seul thread peut occuper le verrou en lecture-écriture en mode écriture à la fois, mais plusieurs threads peuvent détenir le verrou en lecture-écriture en mode lecture en même temps. Par conséquent, les verrous en lecture-écriture permettent un parallélisme plus élevé que les mutex. Les verrous en lecture-écriture conviennent parfaitement aux situations où le nombre de lectures de la structure de données est bien supérieur au nombre d'écritures.
Méthodes associées :
// 创建读写锁 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); // 尝试加写锁,非阻塞
Exemples :
#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; }
屏障(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!