Heim  >  Artikel  >  Betrieb und Instandhaltung  >  Welche verschiedenen Methoden der Multithread-Synchronisierung unter Linux gibt es?

Welche verschiedenen Methoden der Multithread-Synchronisierung unter Linux gibt es?

青灯夜游
青灯夜游Original
2023-03-10 10:44:352273Durchsuche

Zu den Synchronisierungsmethoden gehören: 1. Mutex ist eine spezielle globale Variable mit zwei Zuständen: Sperren und Entsperren. Der Mutex kann von einem Thread abgerufen werden, und die Sperre wird gesperrt Danach hat nur der Thread die Möglichkeit, die Sperre zu öffnen. 2. Die Spin-Sperre ist eine Endlosschleife, die ständig abfragt. 3. Der Semaphor ist ein Zähler, der den Zugriff begrenzt Ressourcen; 4. Bedingungsvariablen; 6. Barriere, ein Synchronisationsmechanismus für Benutzer, um die parallele Arbeit mehrerer Threads zu koordinieren.

Welche verschiedenen Methoden der Multithread-Synchronisierung unter Linux gibt es?

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

Thread-Synchronisation bedeutet, dass, wenn ein Thread eine bestimmte kritische Ressource bearbeitet, andere Threads diese Ressource nicht bearbeiten können, bis der Thread den Vorgang abgeschlossen hat, d Der Lauf erfolgt in einer vorgegebenen Reihenfolge. Es gibt 6 Methoden der Thread-Synchronisation: Mutex-Sperre, Spin-Sperre, Semaphor, Bedingungsvariable, Lese-/Schreibsperre und Barriere.

Linux-Thread-Synchronisierungsmethode

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.

1. Mutex-Sperre

Das Wesentliche einer Mutex-Sperre ist eine spezielle globale Variable, die zwei Zustände hat: Sperren und Entsperren. Der entsperrte Mutex kann von einem bestimmten Thread erhalten werden Thread, Nach dem Halten wird der Mutex gesperrt und wechselt in den Sperrzustand. Danach kann nur der Thread die Sperre öffnen, und andere Threads, die den Mutex erhalten möchten, werden blockiert, bis der Mutex 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;
}

2. Spin-Lock ist eine Endlosschleife, die kontinuierlich abfragt nicht Es wird wie eine Mutex-Sperre in einen blockierenden Ruhezustand versetzt, sondern kontinuierlich abgefragt, um die Sperre zu erhalten. Wenn die Spin-Sperre über einen längeren Zeitraum nicht freigegeben werden kann, ist die Leistung sehr hoch , sogar Es gibt auch eine große Menge an E/A-Blockierungen, die dazu führen, dass andere Threads, die die Sperre erhalten, kontinuierlich abfragen, was dazu führt, dass die CPU-Auslastung 100 % erreicht, insbesondere die CPU-Zeit.

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

3. SemaphoreDas Semaphor ist ein Zähler, der verwendet wird, um die Anzahl der Threads zu steuern, die auf begrenzte gemeinsam genutzte Ressourcen 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;
}

4. Bedingungsvariablen ermöglichen die Ausführung des aufrufenden Threads, wenn bestimmte Bedingungen erfüllt sind, blockiert er und wartet darauf, geweckt zu werden. Es muss mit einem Mutex gesperrt werden. Gemeinsam verwenden. 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);

Beispiele:

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

Das obige ist der detaillierte Inhalt vonWelche verschiedenen Methoden der Multithread-Synchronisierung unter Linux gibt es?. 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