Linux 다중 스레드 프로그래밍에서 잠금은 스레드 간의 경쟁과 교착 상태를 피할 수 있는 매우 중요한 메커니즘입니다. 그러나 잠금을 잘못 사용하면 성능이 저하되고 불규칙한 동작이 발생할 수 있습니다. 이 기사에서는 Linux의 일반적인 잠금 유형, 이를 올바르게 사용하는 방법, 경합 및 교착 상태와 같은 문제를 피하는 방법을 소개합니다.
프로그래밍에서는 공유 데이터 작업의 무결성을 보장하기 위해 객체 뮤텍스 잠금 개념이 도입되었습니다. 각 개체는 "뮤텍스 잠금"이라는 표시에 해당하며, 이는 언제든지 하나의 스레드만 해당 개체에 액세스할 수 있도록 하는 데 사용됩니다. Linux에서 구현하는 뮤텍스 잠금 메커니즘에는 POSIX 뮤텍스 잠금과 커널 뮤텍스 잠금이 포함됩니다. 이 기사에서는 주로 POSIX 뮤텍스 잠금, 즉 스레드 간 뮤텍스 잠금에 대해 설명합니다.
세마포어는 다중 스레드 및 다중 작업 동기화에 사용됩니다. 하나의 스레드가 특정 작업을 완료하면 세마포어를 통해 다른 스레드에 알리고, 다른 스레드는 특정 작업을 수행합니다(모두가 sem_wait에 있으면 거기서 차단됩니다). . 뮤텍스 잠금은 다중 스레드 및 다중 작업 상호 배제에 사용됩니다. 한 스레드가 특정 리소스를 점유하면 이 스레드가 잠금 해제될 때까지 다른 스레드가 해당 리소스에 액세스할 수 없습니다. 예를 들어 전역 변수에 액세스하려면 작업이 완료된 후 잠금 및 잠금 해제가 필요한 경우가 있습니다. 잠금과 세마포어가 동시에 사용되는 경우도 있습니다.”
즉, 세마포어는 반드시 특정 리소스를 잠그는 것이 아니라 프로세스 개념을 잠그는 것입니다. 예를 들어 두 개의 스레드 A와 B가 있습니다. 스레드 B는 다음 단계를 진행하기 전에 스레드 A가 특정 작업을 완료할 때까지 기다려야 합니다. 이 작업에는 반드시 특정 리소스를 잠그는 것이 포함되지 않지만 일부 계산이나 데이터 처리를 수행하는 작업이 포함될 수도 있습니다. 스레드 뮤텍스는 "특정 리소스를 잠그는 것"이라는 개념입니다. 잠금 기간 동안 다른 스레드는 보호된 데이터에 대해 작업을 수행할 수 없습니다. 어떤 경우에는 두 가지가 상호 교환 가능합니다.
둘 사이의 차이점:
범위
세마포어: 프로세스 간 또는 스레드 간(리눅스에서만 스레드 간)
뮤텍스 잠금: 스레드 간
잠긴 경우
세마포어: 세마포어 값이 0보다 크면 다른 스레드가 성공적으로 sem_wait할 수 있습니다. 성공 후에는 세마포어 값이 1만큼 감소합니다. 값이 0보다 크지 않으면 sem_post가 해제된 후 값이 1만큼 증가할 때까지 sem_wait가 차단됩니다. 한마디로 세마포어의 값>=0이다.
뮤텍스 잠금: 잠겨 있는 한 다른 스레드는 보호된 리소스에 액세스할 수 없습니다. 잠금이 없으면 리소스를 성공적으로 얻은 것입니다. 그렇지 않으면 리소스를 사용할 수 있을 때까지 차단하고 기다립니다. 한마디로 스레드 뮤텍스의 값은 음수가 될 수 있습니다.
멀티스레딩
스레드는 컴퓨터에서 독립적으로 실행되는 가장 작은 단위이며 실행 시 시스템 리소스를 거의 차지하지 않습니다. 멀티 프로세스와 비교할 때 멀티 프로세스에는 멀티 프로세스에는 없는 몇 가지 장점이 있습니다. 가장 중요한 것은 멀티 스레딩의 경우 멀티 프로세스보다 리소스를 더 절약할 수 있다는 것입니다.
스레드 생성
Linux에서는 새로 생성된 스레드가 원래 프로세스에 없지만 시스템이 시스템 호출을 통해 clone()을 호출합니다. 시스템은 원본 프로세스와 정확히 동일한 프로세스를 복사하고 이 프로세스에서 스레드 기능을 실행합니다.
Linux에서는 pthread_create() 함수를 통해 스레드 생성이 이루어집니다.
pthread_create()
으아악그 중:
thread는 pthread_t 유형의 포인터를 나타냅니다.
attr은 스레드의 일부 속성을 지정하는 데 사용됩니다.
start_routine은 함수를 호출하는 스레드인 함수 포인터를 나타냅니다.
arg는 스레드 호출 함수에 전달된 매개변수를 나타냅니다.
스레드가 성공적으로 생성되면 pthread_create() 함수는 0을 반환합니다. 반환값이 0이 아니면 스레드 생성에 실패한 것입니다. 스레드 속성의 경우 pthread_attr_t 구조에 정의됩니다.
스레드 생성 과정은 다음과 같습니다.
으아악위 코드에서는 pthread_self() 함수가 사용됩니다. 이 함수의 기능은 이 스레드의 스레드 ID를 얻는 것입니다. 메인 함수의 sleep()은 스레드 실행이 완료될 수 있도록 메인 프로세스를 대기 상태로 만드는 데 사용됩니다. 최종 실행 효과는 다음과 같습니다.
그렇다면 arg를 사용하여 하위 스레드에 매개변수를 전달하는 방법은 무엇입니까? 구체적인 구현은 다음과 같습니다.
으아악최종 실행 효과는 아래 그림과 같습니다.
다음 코드와 같이 메인 프로세스가 일찍 종료되면 어떻게 될까요? 으아악
이때 메인 프로세스는 일찍 종료되고 프로세스는 자원을 재활용하게 됩니다. 이때 모든 스레드는 실행을 종료하게 됩니다.
스레드가 멈췄습니다
위 구현 프로세스에서 메인 스레드가 종료되기 전에 각 하위 스레드가 완료될 때까지 기다리도록 하기 위해 Linux 멀티스레딩에서는 pthread_join() 함수를 사용하여 사용할 수도 있습니다. 다른 스레드를 기다리세요. 함수의 구체적인 형식은 다음과 같습니다.으아악
pthread_join() 함수는 스레드의 끝을 기다리는 데 사용되며 해당 호출은 일시 중단됩니다.
一个线程仅允许一个线程使用pthread_join()等待它的终止。
如需要在主线程中等待每一个子线程的结束,如下述代码所示:
#include #include #include #include void* thread(void *id){ pthread_t newthid; newthid = pthread_self(); int num = *(int *)id; printf("this is a new thread, thread ID is %u,id:%d\n", newthid, num); printf("thread %u is done\n", newthid); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); printf("main thread, ID is %u\n", pthread_self()); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } for (int i = 0; i return 0; }
最终的执行效果如下所示:
注:在编译的时候需要链接libpthread.a:
g++ xx.c -lpthread -o xx
互斥锁mutex
多线程的问题引入
多线程的最大的特点是资源的共享,但是,当多个线程同时去操作(同时去改变)一个临界资源时,会破坏临界资源。如利用多线程同时写一个文件:
#include #include const char filename[] = "hello"; void* thread(void *id){ int num = *(int *)id; // 写文件的操作 FILE *fp = fopen(filename, "a+"); int start = *((int *)id); int end = start + 1; setbuf(fp, NULL);// 设置缓冲区的大小 fprintf(stdout, "%d\n", start); for (int i = (start * 10); i "%d\t", i); } fprintf(fp, "\n"); fclose(fp); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } for (int i = 0; i return 0; }
执行以上的代码,我们会发现,得到的结果是混乱的,出现上述的最主要的原因是,我们在编写多线程代码的过程中,每一个线程都尝试去写同一个文件,这样便出现了上述的问题,这便是共享资源的同步问题,在Linux编程中,线程同步的处理方法包括:信号量,互斥锁和条件变量。
互斥锁
互斥锁是通过锁的机制来实现线程间的同步问题。互斥锁的基本流程为:
初始化一个互斥锁:pthread_mutex_init()函数
加锁:pthread_mutex_lock()函数或者pthread_mutex_trylock()函数
对共享资源的操作
解锁:pthread_mutex_unlock()函数
注销互斥锁:pthread_mutex_destory()函数
其中,在加锁过程中,pthread_mutex_lock()函数和pthread_mutex_trylock()函数的过程略有不同:
当使用pthread_mutex_lock()函数进行加锁时,若此时已经被锁,则尝试加锁的线程会被阻塞,直到互斥锁被其他线程释放,当pthread_mutex_lock()函数有返回值时,说明加锁成功;
而使用pthread_mutex_trylock()函数进行加锁时,若此时已经被锁,则会返回EBUSY的错误码。
同时,解锁的过程中,也需要满足两个条件:
解锁前,互斥锁必须处于锁定状态;
必须由加锁的线程进行解锁。
当互斥锁使用完成后,必须进行清除。
有了以上的准备,我们重新实现上述的多线程写操作,其实现代码如下所示:
#include #include pthread_mutex_t mutex; const char filename[] = "hello"; void* thread(void *id){ int num = *(int *)id; // 加锁 if (pthread_mutex_lock(&mutex) != 0){ fprintf(stdout, "lock error!\n"); } // 写文件的操作 FILE *fp = fopen(filename, "a+"); int start = *((int *)id); int end = start + 1; setbuf(fp, NULL);// 设置缓冲区的大小 fprintf(stdout, "%d\n", start); for (int i = (start * 10); i "%d\t", i); } fprintf(fp, "\n"); fclose(fp); // 解锁 pthread_mutex_unlock(&mutex); return NULL; } int main(){ int num_thread = 5; pthread_t *pt = (pthread_t *)malloc(sizeof(pthread_t) * num_thread); int * id = (int *)malloc(sizeof(int) * num_thread); // 初始化互斥锁 if (pthread_mutex_init(&mutex, NULL) != 0){ // 互斥锁初始化失败 free(pt); free(id); return 1; } for (int i = 0; i if (pthread_create(&pt[i], NULL, thread, &id[i]) != 0){ printf("thread create failed!\n"); return 1; } } for (int i = 0; i return 0; }
最终的结果为:
通过本文的介绍,您应该已经了解了Linux多线程编程中的常见锁类型、正确使用锁的方法以及如何避免竞争和死锁等问题。锁机制是多线程编程中必不可少的一部分,掌握它们可以使您的代码更加健壮和可靠。在实际应用中,应该根据具体情况选择合适的锁类型,并遵循最佳实践,以确保程序的高性能和可靠性。
위 내용은 Linux 다중 스레드 프로그래밍 잠금에 대한 자세한 설명: 경합 및 교착 상태를 피하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!