6가지 방법: 1. 기본적으로 잠금 및 잠금 해제의 두 가지 상태를 갖는 특수 전역 변수인 뮤텍스 잠금 2. 무한 루프이며 폴링을 유지하는 스핀 잠금 3. 세마포어, 스레드 수를 제어하는 데 사용됩니다. 제한된 공유 리소스에 액세스 4. 특정 조건이 충족되면 호출 스레드가 실행되고 조건이 충족되지 않으면 깨어나도록 기다리는 조건 변수 5. 읽기-쓰기 잠금, 스레드는 하나만 있을 수 있습니다. 6. 장벽은 사용자가 여러 스레드의 병렬 작업을 조정하는 동기화 메커니즘입니다.
이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.
다음은 스레드가 안전하지 않은 예입니다.
#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는 스레드 동기화를 처리하는 다양한 방법을 제공하며 가장 일반적으로 사용되는 방법은 뮤텍스 잠금, 스핀 잠금 및 세마포어입니다.
뮤텍스 잠금의 본질은 잠금과 잠금 해제의 두 가지 상태를 갖는 특수 전역 변수입니다. 잠금 해제된 뮤텍스는 스레드가 뮤텍스를 보유할 때 획득할 수 있습니다. 그 이후에는 스레드만이 잠금을 열 수 있는 권한을 가지며, 뮤텍스 잠금이 해제될 때까지 뮤텍스 잠금을 획득하려는 다른 스레드는 차단됩니다.
뮤텍스 잠금 유형:
일반 잠금(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; }
이름에서 알 수 있듯이 스핀 잠금은 무한 루프이며 스레드가 계속해서 폴링을 수행하지 않으면 동작하지 않습니다. 마찬가지로 mutex lock도 blocking sleep 상태에 들어가지만 lock을 획득하기 위해 지속적으로 poll을 하게 되는데, spin lock을 빨리 해제할 수 있다면 성능이 매우 높을 것이고, 오랫동안 spin lock을 해제하지 못하는 경우도 있을 수 있습니다. 내부에 뭔가가 있을 수 있습니다. 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; }
세마포는 제한된 공유 리소스에 액세스하는 스레드 수를 제어하는 데 사용되는 카운터입니다.
관련 메소드:
// 创建信号量 // 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; }
조건 변수는 특정 조건이 충족되지 않을 때 호출 스레드가 실행되도록 허용하며, 이를 차단하고 깨어날 때까지 기다려야 합니다. 뮤텍스 잠금과 함께 사용됩니다.
조건 변수는 생산자 및 소비자 모델에서 자주 사용됩니다.
관련 방법:
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(¬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; }
읽기-쓰기 잠금에는 읽기 모드의 잠금 상태, 쓰기 모드의 잠금 상태, 잠금 해제 상태의 세 가지 상태가 있을 수 있습니다. 한 번에 하나의 스레드만 쓰기 모드에서 읽기-쓰기 잠금을 보유할 수 있지만, 여러 스레드가 읽기 모드에서 동시에 읽기-쓰기 잠금을 보유할 수 있습니다. 읽기-쓰기 잠금은 공유-배타적 잠금이라고도 합니다. 읽기-쓰기 잠금이 읽기 모드에서 잠기면 공유 모드에서도 잠깁니다. 쓰기 잠금 공유, 읽기 및 쓰기가 상호 배타적입니다.
쓰기 모드에서는 한 번에 하나의 스레드만 읽기-쓰기 잠금을 차지할 수 있지만, 읽기 모드에서는 여러 스레드가 동시에 읽기-쓰기 잠금을 유지할 수 있습니다. 따라서 읽기-쓰기 잠금은 뮤텍스에 비해 더 높은 병렬 처리를 허용합니다. 읽기-쓰기 잠금은 데이터 구조에 대한 읽기 횟수가 쓰기 횟수보다 훨씬 많은 상황에 매우 적합합니다.
관련 방법:
// 创建读写锁 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; }
屏障(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 중국어 웹사이트의 기타 관련 기사를 참조하세요!