Heim  >  Artikel  >  Betrieb und Instandhaltung  >  Es gibt mehrere Möglichkeiten, die Thread-Synchronisierung unter Linux zu implementieren

Es gibt mehrere Möglichkeiten, die Thread-Synchronisierung unter Linux zu implementieren

青灯夜游
青灯夜游Original
2022-07-01 19:39:234882Durchsuche

6 Möglichkeiten: 1. Mutex-Sperre, die im Wesentlichen eine spezielle globale Variable mit zwei Zuständen ist: Sperren und Entsperren. 2. Spin-Sperre, die eine Endlosschleife ist und ständig abfragt. 3. Semaphor, wird zur Steuerung der Anzahl der Threads verwendet Zugriff auf begrenzte gemeinsam genutzte Ressourcen; 4. Bedingungsvariablen, die es dem aufrufenden Thread ermöglichen, zu laufen, wenn bestimmte Bedingungen erfüllt sind, und zu blockieren und auf das Erwachen zu warten, wenn die Bedingungen nicht erfüllt sind. 5. Lese-/Schreibsperre, es kann nur einen Thread geben auf einmal Kann im Schreibmodus 6 Lese-/Schreibsperren belegen. Barrier ist ein Synchronisationsmechanismus, mit dem Benutzer die parallele Arbeit mehrerer Threads koordinieren können.

Es gibt mehrere Möglichkeiten, die Thread-Synchronisierung unter Linux zu implementieren

Die Betriebsumgebung dieses Tutorials: Linux7.3-System, Dell G3-Computer.

6 Möglichkeiten zur Thread-Synchronisierung unter Linux

Das Folgende ist ein Thread-unsicheres Beispiel:

#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;
}

Das laufende Ergebnis lautet wie folgt:

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

Das endgültige laufende Ergebnis ist nicht festgelegt, es kann 0 sein, - 1. Wenn die Variable „ticket_num“ den Bestand darstellt, ist der Bestand negativ. Daher muss eine Thread-Synchronisierung eingeführt werden, um die Thread-Sicherheit sicherzustellen.

Linux bietet verschiedene Möglichkeiten zur Thread-Synchronisierung. Die am häufigsten verwendeten sind Mutex-Sperren, Spin-Sperren und Semaphoren.

Mutex-Sperre

Das Wesentliche einer Mutex-Sperre ist eine spezielle globale Variable mit zwei Zuständen: Sperren und Entsperren. Der entsperrte Mutex kann von einem Thread abgerufen werden wird gesperrt und in den Sperrzustand versetzt. Danach kann nur der Thread die Sperre öffnen, und andere Threads, die die Mutex-Sperre erhalten möchten, werden blockiert, bis die Mutex-Sperre entsperrt wird.

Typ der Mutex-Sperre:

  • Normale Sperre (PTHREAD_MUTEX_NORMAL): Standardtyp der Mutex-Sperre. Wenn ein Thread eine gemeinsame Sperre sperrt, bilden die verbleibenden Threads, die die Sperre anfordern, eine Warteschlange und erhalten die Sperre entsprechend ihrer Priorität, nachdem sie entsperrt wurde. Dieser Sperrtyp gewährleistet eine faire Ressourcenzuweisung. Wenn ein Thread eine normale Sperre erneut sperrt, führt dies zu einem Deadlock. Das Entsperren einer normalen Sperre, die von einem anderen Thread gesperrt wurde, oder das erneute Entsperren einer normalen Sperre führt zu unvorhersehbaren Konsequenzen.

  • Fehlerüberprüfungssperre (PTHREAD_MUTEX_ERRORCHECK): Wenn ein Thread eine bereits gesperrte Fehlerüberprüfungssperre erneut sperrt, gibt der Sperrvorgang EDEADLK zurück; entsperrt eine Fehlerüberprüfungssperre, die von einem anderen Thread gesperrt wurde, oder entsperrt eine Wird die bereits entriegelte Fehlererkennungssperre wieder entriegelt, kehrt der Entriegelungsvorgang zu EPERM zurück.

  • Verschachtelte Sperre (PTHREAD_MUTEX_RECURSIVE): Diese Sperre ermöglicht es einem Thread, sie mehrmals zu sperren, bevor sie ohne Deadlock freigegeben wird. Damit andere Threads diese Sperre erhalten, muss der Besitzer der aktuellen Sperre mehrere Entsperrvorgänge durchführen, um eine verschachtelte Sperre zu entsperren Um eine Sperre zu öffnen, die von einem anderen Thread gesperrt wurde, oder um eine bereits entsperrte verschachtelte Sperre wieder zu entsperren, gibt der Entsperrvorgang EPERM zurück.

  • Standardsperre (PTHREAD_MUTEX_DEFAULT): Wenn ein Thread eine bereits gesperrte Standardsperre erneut sperrt oder eine Standardsperre entsperrt, die von einem anderen Thread gesperrt wurde, oder eine entsperrte Standardsperre entsperrt, führt dies zu unvorhersehbaren Konsequenzen kann bei der Implementierung einer der oben genannten drei Sperren zugeordnet werden.

Verwandte Methoden:

// 静态方式创建互斥锁
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); // 解锁

Beispiel:

#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;
}

Spin-Lock

Wie der Name schon sagt, ist Spin-Lock eine Endlosschleife, die kontinuierlich abfragt. Wenn ein Thread den Spin-Lock nicht erhält, verhält er sich nicht Die Mutex-Sperre wechselt ebenfalls in den blockierenden Ruhezustand, fragt jedoch kontinuierlich nach der Sperre. Wenn die Spin-Sperre schnell aufgehoben werden kann, ist die Leistung möglicherweise sehr hoch Es gibt eine große Menge an E/A-Blockierungen, die dazu führen, dass andere Threads, die Sperren erwerben, kontinuierlich abfragen, wodurch die CPU-Auslastung, insbesondere die CPU-Zeit, 100 % erreicht.

Verwandte Methoden:

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); // 解锁

Beispiel:

#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;
}

Semaphore

Ein Semaphor ist ein Zähler, der verwendet wird, um die Anzahl der Threads zu steuern, die auf eine begrenzte gemeinsam genutzte Ressource zugreifen.

Verwandte Methoden:

// 创建信号量
// 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

Beispiel:

#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;
}

Bedingte Variablen ermöglichen die Ausführung des aufrufenden Threads, wenn bestimmte Bedingungen erfüllt sind. Wenn die Bedingungen nicht erfüllt sind, blockiert er und wartet darauf, aktiviert zu werden mit einer Mutex-Sperre verwendet werden.

Bedingte Variablen werden häufig in Produzenten- und Verbrauchermodellen verwendet.

Verwandte Methoden:

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);
Beispiel:

#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;
}

Lese-/Schreibsperre

Die Lese-/Schreibsperre kann drei Zustände haben: gesperrter Zustand im Lesemodus, gesperrter Zustand im Schreibmodus und entsperrter Zustand. Es kann jeweils nur ein Thread eine Lese-/Schreibsperre im Schreibmodus halten, aber mehrere Threads können gleichzeitig eine Lese-/Schreibsperre im Lesemodus halten. Eine Lese-/Schreibsperre wird auch als gemeinsam genutzte Sperre bezeichnet. Wenn eine Lese-/Schreibsperre im Lesemodus gesperrt ist, ist sie im Schreibmodus gesperrt. Schreibsperre Gemeinsam genutzt, Lesen und Schreiben schließen sich gegenseitig aus.

Im Schreibmodus kann jeweils nur ein Thread die Lese-/Schreibsperre belegen, aber mehrere Threads können gleichzeitig im Lesemodus die Lese-/Schreibsperre halten. Daher ermöglichen Lese-/Schreibsperren im Vergleich zu Mutexen eine höhere Parallelität. Lese-/Schreibsperren eignen sich sehr gut für Situationen, in denen die Anzahl der Lesevorgänge in der Datenstruktur viel größer ist als die Anzahl der Schreibvorgänge.

Verwandte Methoden:

// 创建读写锁
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); // 尝试加写锁,非阻塞
Beispiele:

#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视频教程

Das obige ist der detaillierte Inhalt vonEs gibt mehrere Möglichkeiten, die Thread-Synchronisierung unter Linux zu implementieren. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn