一、什么是互斥锁?
互斥锁(Mutex,全名为 Mutual Exclusion)是一种线程同步的机制,用于保护共享资源,确保同一时间只有一个线程可以访问共享资源,从而避免多个线程同时修改共享资源导致的数据不一致性和竞态条件。
互斥锁的基本操作包括两个主要函数:锁定(Lock)和解锁(Unlock)。当一个线程要访问共享资源时,首先会尝试获取互斥锁,如果互斥锁已经被其他线程获取,则当前线程会被阻塞,直到获取到互斥锁为止。获取到互斥锁后,线程可以安全地访问共享资源,执行完操作后,需要释放互斥锁,以允许其他线程继续访问共享资源。
互斥锁的特点包括:
独占性(Exclusive Access): 在任意时刻,只有一个线程可以持有互斥锁,其他线程需要等待该线程释放锁后才能获取锁。
阻塞(Blocking): 如果一个线程尝试获取已经被其他线程持有的互斥锁,那么该线程会被阻塞,直到互斥锁被释放。
递归性(Recursive): 同一个线程可以多次获取同一个互斥锁,但需要相应数量的解锁操作才能释放锁。
死锁避免(Deadlock Avoidance): 一般来说,互斥锁的实现会避免死锁的发生,即系统会在检测到死锁的可能性时,选择其中一个线程进行阻塞,以打破死锁。
使用互斥锁时需要注意以下几点:
- 必须确保每次对共享资源的访问都在互斥锁的保护下进行。
- 获取互斥锁后,应该尽快释放锁,避免长时间持有锁导致其他线程的阻塞。
- 应该避免在持有互斥锁时调用可能导致阻塞的操作,以免造成死锁。
二、互斥锁初始化
在使用互斥锁(Mutex)之前,需要对互斥锁进行初始化。在C语言中,通常使用 pthread_mutex_init()
函数来初始化互斥锁。这个函数的原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
mutex
:指向要初始化的互斥锁的指针。attr
:指向互斥锁属性的指针,通常传递 NULL 表示使用默认属性。
初始化互斥锁时,可以选择是否指定互斥锁的属性。如果不需要特定的属性,可以将 attr
参数设置为 NULL。如果需要自定义属性,需要首先创建并初始化 pthread_mutexattr_t
类型的变量,并设置相应的属性,然后将其传递给 pthread_mutex_init()
函数。
以下是一个简单的示例,演示了如何初始化互斥锁:
#include <stdio.h>
#include <pthread.h>
int main() {
pthread_mutex_t mutex; // 定义互斥锁变量
// 初始化互斥锁
if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("pthread_mutex_init");
return -1;
}
// 互斥锁已经初始化完成,可以在此之后使用互斥锁
// 销毁互斥锁(如果不再需要)
pthread_mutex_destroy(&mutex);
return 0;
}
在这个示例中,首先定义了一个 pthread_mutex_t
类型的变量 mutex
,然后通过调用 pthread_mutex_init()
函数初始化了这个互斥锁。初始化完成后,可以在程序中的其他地方使用这个互斥锁来保护共享资源。最后,通过调用 pthread_mutex_destroy()
函数销毁互斥锁,释放相关资源。
三、互斥锁加锁和解锁
在使用互斥锁(Mutex)保护临界区时,需要在临界区的访问操作前后分别进行加锁和解锁操作,以确保同一时间只有一个线程可以访问临界区。在C语言中,可以使用pthread_mutex_lock()
函数进行加锁操作,使用 pthread_mutex_unlock()
函数进行解锁操作。
pthread_mutex_lock()
函数是用于对互斥锁进行加锁的函数,在多线程程序中用于保护临界区,确保同一时间只有一个线程可以进入临界区执行操作,避免数据竞争和数据不一致性的问题。该函数的原型如下:
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
-
mutex
:指向要加锁的互斥锁的指针。
pthread_mutex_lock()
函数尝试对指定的互斥锁进行加锁操作。如果互斥锁当前处于未加锁状态,调用线程将成功获取互斥锁,并且该函数立即返回,返回值为 0。如果互斥锁当前被其他线程持有,则调用线程将被阻塞,直到获取到互斥锁为止。
pthread_mutex_unlock()
函数用于对互斥锁进行解锁操作,在多线程程序中,它被用于释放互斥锁,允许其他线程进入临界区执行操作。该函数的原型如下:
#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
-
mutex
:指向要解锁的互斥锁的指针。
pthread_mutex_unlock()
函数尝试对指定的互斥锁进行解锁操作。如果调用线程当前持有指定的互斥锁,并且成功释放了锁,则函数返回 0。如果调用线程没有持有指定的互斥锁,或者出现其他错误,则函数返回相应的错误码。
以下是互斥锁加锁和解锁的示例代码:
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex; // 全局互斥锁变量
void *thread_function(void *arg) {
// 加锁
pthread_mutex_lock(&mutex);
// 访问临界区
printf("线程开始执行临界区操作\n");
// 假设这里是临界区的操作
// 解锁
pthread_mutex_unlock(&mutex);
return NULL;
}
int main() {
pthread_t thread;
// 初始化互斥锁
if (pthread_mutex_init(&mutex, NULL) != 0) {
perror("pthread_mutex_init");
return -1;
}
// 创建线程
if (pthread_create(&thread, NULL, thread_function, NULL) != 0) {
perror("pthread_create");
return -1;
}
// 加锁
pthread_mutex_lock(&mutex);
// 访问临界区
printf("主线程开始执行临界区操作\n");
// 假设这里是临界区的操作
// 解锁
pthread_mutex_unlock(&mutex);
// 等待线程结束
pthread_join(thread, NULL);
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
在这个示例中,我们在主线程和子线程中都对共享资源进行了访问操作,其中访问操作前后分别使用 pthread_mutex_lock()
函数进行加锁和 pthread_mutex_unlock()
函数进行解锁。这样可以确保在任意时刻,只有一个线程可以访问临界区,避免了数据不一致性和竞态条件的问题。
运行结果如下:
做大做强,再创辉煌!