互斥锁
1. 基本概念
互斥锁是一种用于在多线程环境下保证临界区资源只有一个线程能够访问的同步技术。它通常用于在两个或多个线程之间进行互斥操作,以避免竞争条件的产生。
互斥锁有两种类型:
- 共享互斥锁(Shared Mutex):可以被多个线程同时访问的互斥锁,通常用于对共享资源的互斥操作。
- 排它互斥锁(Exclusive Mutex):只能被一个线程访问的互斥锁,通常用于对专用资源的互斥操作。
对互斥锁的操作步骤:
- 在程序中定义一个互斥量变量,使用
pthread_mutex_t
类型。 - 使用
pthread_mutex_init()
函数来初始化互斥量(初始化)。 - 在需要保护的临界区调用
pthread_mutex_lock()
函数获取互斥量(加锁)。 - 在临界区内进行需要互斥的操作。
- 在临界区结束后调用
pthread_mutex_unlock()
函数释放互斥量(解锁)。 - 使用完后调用
pthread_mutex_destroy()
函数来销毁互斥量。
2. 互斥锁API
2.1 定义互斥锁变量并初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义一个互斥锁变量,并初始化
这种方法等价于使用pthread_mutex_init()函数初始化互斥锁,但是更加简洁。
注意:使用PTHREAD_MUTEX_INITIALIZER宏初始化互斥锁时,需要保证该互斥锁变量是全局变量或者静态变量。
2.2 初始化互斥锁
在 Linux 系统中 pthread_mutex_init()
函数用于初始化互斥量。函数原型为:
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
该函数接受两个参数:
mutex
:指向互斥量的指针。attr
:指向互斥量属性的指针。如果初始化成功,则该函数返回 0。否则,返回一个非 0 值。
例如,下面的代码演示了如何初始化一个互斥量:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
//定义一个互斥锁变量
pthread_mutex_t mutex;
int main(int argc, char *argv[])
{
// 初始化互斥量
int ret = pthread_mutex_init(&mutex, NULL);
if (ret != 0) {
// 初始化失败
perror("pthread_mutex_init() fail");
exit(EXIT_FAILURE);
}
// 使用互斥量
}
2.3 加锁
pthread_mutex_lock()
函数用于获取互斥锁。函数原型如下:
int pthread_mutex_lock(pthread_mutex_t *mutex);
该函数接受一个参数:
mutex
:指向互斥锁的指针。如果获取互斥锁成功,则该函数返回 0。否则,返回一个非 0 值。
2.4 解锁
pthread_mutex_unlock()
函数用于释放互斥锁。函数原型如下:
int pthread_mutex_unlock(pthread_mutex_t *mutex);
该函数接受一个参数:
mutex
:指向互斥锁的指针。如果释放互斥锁成功,则该函数返回 0。否则,返回一个非 0 值。
2.5 销毁锁
pthread_mutex_destroy()
函数用于销毁互斥锁。函数原型如下:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
该函数接受一个参数:
mutex
:指向互斥锁的指针。如果销毁互斥锁成功,则该函数返回 0。否则,返回一个非 0 值。
在销毁互斥锁时,需要注意以下几点:
- 互斥锁必须已被初始化,否则销毁互斥锁会失败。
- 互斥锁必须已被锁定,并且没有任何线程在等待获取该锁,否则销毁互斥锁会失败。
- 一旦销毁了互斥锁,它就无法再被使用。如果试图使用已销毁的互斥锁,可能会导致程序崩溃或其他未定义的行为。
2.6 初始化、加锁、解锁、销毁示例代码
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
//定义一个互斥锁变量
pthread_mutex_t mutex;
void *thread_func(void *arg)
{
const char *str = (char *)arg;
// 加锁,使用共享资源
int ret = pthread_mutex_lock(&mutex);
if (ret != 0) {
// 加锁失败
perror("pthread_mutex_lock() fail");
exit(EXIT_FAILURE);
}
while(*str != '\0'){
fprintf(stderr, "%c", *str++);
usleep(100000);
}
//已经使用完共享资源, 解锁
ret = pthread_mutex_unlock(&mutex);
if (ret != 0) {
// 加锁失败
perror("pthread_mutex_unlock() fail");
exit(EXIT_FAILURE);
}
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
// 初始化互斥量
int ret = pthread_mutex_init(&mutex, NULL);
if (ret != 0) {
// 初始化失败
perror("pthread_mutex_init() fail");
exit(EXIT_FAILURE);
}
// 使用互斥量
pthread_t attr1, attr2;
pthread_create(&attr1, NULL, thread_func, (void *)"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n");
pthread_create(&attr2, NULL, thread_func, (void *)"yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy\n");
// 等待所有线程结束
pthread_join(attr1, NULL);
pthread_join(attr2, NULL);
// 销毁互斥量
ret = pthread_mutex_destroy(&mutex);
if (ret != 0) {
// 销毁互斥量失败
perror("pthread_mutex_destroy() fail");
}
pthread_exit(NULL);
}
3. 死锁
死锁是指两个或多个线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。在这种情况下,线程无法推进下去,无法继续执行,整个程序也无法继续执行下去。
要避免死锁,可以采取以下措施:
- 加锁顺序:线程在获取多个锁时,应该按照一定的顺序来加锁,避免出现相互等待的情况。
- 加锁时间:在线程使用完共享资源后,应该及时释放锁,避免占用锁的时间过长。
- 加锁范围:线程在使用共享资源时,应该尽量缩小加锁的范围,以减少对其他线程的影响。