>  기사  >  운영 및 유지보수  >  Linux에서 다중 스레드 동기화를 수행하는 몇 가지 방법은 무엇입니까?

Linux에서 다중 스레드 동기화를 수행하는 몇 가지 방법은 무엇입니까?

青灯夜游
青灯夜游원래의
2023-03-10 10:44:352264검색

동기화 방법은 다음과 같습니다. 1. 뮤텍스는 잠금 및 잠금 해제의 두 가지 상태를 갖는 특수 전역 변수입니다. 잠금 해제 뮤텍스는 스레드에 의해 획득될 수 있으며 뮤텍스는 잠금이 설정되고 설정됩니다. 그 후에는 스레드만이 잠금을 열 수 있습니다. 2. 스핀 잠금은 지속적으로 폴링하는 무한 루프입니다. 3. 세마포어는 제한된 공유 스레드 수를 제어하는 ​​데 사용됩니다. 4. 조건 변수 5. 읽기-쓰기 잠금 6. 사용자가 여러 스레드의 병렬 작업을 조정하는 동기화 메커니즘입니다.

Linux에서 다중 스레드 동기화를 수행하는 몇 가지 방법은 무엇입니까?

이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.

스레드 동기화는 스레드가 특정 중요한 리소스에서 작동 중일 때 스레드가 작업을 완료할 때까지 다른 스레드가 이 리소스에서 작동할 수 없음을 의미합니다. 즉, 조정된 속도로 스레드가 작동할 수 있도록 합니다. 미리 정해진 순서대로 실행됩니다. 스레드 동기화에는 뮤텍스 잠금, 스핀 잠금, 세마포어, 조건 변수, 읽기-쓰기 잠금 및 장벽의 6가지 방법이 있습니다.

Linux 스레드 동기화 방법

다음은 스레드가 안전하지 않은 예입니다.

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

실행 결과는 다음과 같습니다.

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

최종 실행 결과는 고정되지 않습니다. 0일 수 있습니다. - 1, ticket_num 변수가 인벤토리를 나타내는 경우 인벤토리는 음수이므로 스레드 안전을 보장하기 위해 스레드 동기화를 도입해야 합니다.

Linux는 스레드 동기화를 처리하는 여러 가지 방법을 제공하며 가장 일반적으로 사용되는 방법은 뮤텍스 잠금, 스핀 잠금 및 세마포어입니다.

1. 뮤텍스 잠금

뮤텍스 잠금의 본질은 잠금과 잠금 해제의 두 가지 상태를 갖는 특수 전역 변수입니다. 잠금 해제된 뮤텍스는 특정 스레드에 의해 획득될 수 있습니다. 스레드가 보유된 후에는 뮤텍스가 잠기고 잠금 상태가 됩니다. 그 후에는 스레드만이 잠금을 열 수 있는 권한을 가지며 뮤텍스를 얻으려는 다른 스레드는 뮤텍스가 잠금 해제될 때까지 차단됩니다.

뮤텍스 잠금 유형:

  • 일반 잠금(PTHREAD_MUTEX_NORMAL): 기본 뮤텍스 잠금 유형입니다. 스레드가 공통 잠금을 잠그면 잠금을 요청하는 나머지 스레드는 대기 큐를 형성하고 잠금이 해제된 후 우선순위에 따라 잠금을 획득합니다. 이 잠금 유형은 자원 할당의 공정성을 보장합니다. 스레드가 잠긴 일반 잠금을 다시 잠그면 교착 상태가 발생합니다. 다른 스레드에 의해 잠긴 일반 잠금을 잠금 해제하거나 다시 잠금 해제된 일반 잠금을 잠금 해제하면 예측할 수 없는 결과가 발생합니다.

  • 오류 검사 잠금(PTHREAD_MUTEX_ERRORCHECK): 스레드가 이미 잠긴 오류 검사 잠금을 다시 잠그면 잠금 작업은 다른 스레드에 의해 잠긴 오류 검사 잠금을 해제하거나 잠금을 해제하는 EDEADLK를 반환합니다. 이미 잠금 해제된 오류 감지 잠금이 다시 잠금 해제되면 잠금 해제 작업이 EPERM으로 돌아갑니다.

  • 중첩 잠금(PTHREAD_MUTEX_RECURSIVE): 이 잠금을 사용하면 스레드가 교착 상태 없이 잠금을 해제하기 전에 여러 번 잠글 수 있습니다. 다른 스레드가 이 잠금을 얻으려면 현재 잠금 소유자가 중첩 잠금을 해제하기 위해 여러 번 잠금 해제 작업을 수행해야 합니다. 다른 스레드에 의해 잠긴 잠금이 있거나 이미 잠금 해제된 중첩 잠금을 다시 잠금 해제하려는 경우 잠금 해제 작업은 EPERM을 반환합니다.

  • 기본 잠금(PTHREAD_MUTEX_DEFAULT): 스레드가 이미 잠긴 기본 잠금을 다시 잠그거나, 다른 스레드에 의해 잠긴 기본 잠금을 잠금 해제하거나, 잠금 해제된 기본 잠금을 잠금 해제하면 예측할 수 없는 결과가 발생합니다. 구현 시 위의 세 가지 잠금 중 하나에 매핑될 수 있습니다.

관련 방법:

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

예:

#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. 스핀 잠금

이름에서 알 수 있듯이 스핀 잠금은 무한 루프이며 스레드가 스핀 잠금을 얻지 못할 경우 계속해서 폴링됩니다. 뮤텍스 잠금처럼 차단 절전 상태로 들어가지만 잠금을 획득하기 위해 지속적으로 폴링합니다. 스핀 잠금을 빠르게 해제할 수 있으면 오랫동안 스핀 잠금을 해제할 수 없으면 성능이 매우 높아집니다. , 심지어 IO 차단도 많아 잠금을 획득하는 다른 스레드가 지속적으로 폴링하게 되어 CPU 사용량, 특히 CPU 시간이 100%에 도달하게 됩니다.

관련 메소드:

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

예:

#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. Semaphore

세마포는 제한된 공유 리소스에 액세스하는 스레드 수를 제어하는 ​​데 사용되는 카운터입니다.

관련 메서드:

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

예:

#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. 조건 변수

조건 변수를 사용하면 특정 조건이 충족되지 않을 때 호출 스레드가 실행될 수 있습니다. 뮤텍스로 잠겨야 합니다. 함께 사용하세요.

조건 변수는 생산자 및 소비자 모델에서 자주 사용됩니다.

관련 방법:

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

예:

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

위 내용은 Linux에서 다중 스레드 동기화를 수행하는 몇 가지 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.