目录
?
- 临界资源:多线程执行流共享的资源就叫做临界资源
- 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
- 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
- 原子性:不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。
当不同进程/线程去访问某个临界资源的时候,就需要进行互斥保护, 这种互斥保护可以看做是一种锁机制,就好比当你去上厕所的时候,你会锁住门,不让别人进来。 在Linux系统中的锁机制是一个比较广泛的概念,而且锁的种类很多,包括互斥锁,文件锁,读写锁等等。
在初始化的时候,互斥锁处于开锁的状态,当互斥锁被线程持有时,该互斥锁处于闭锁状态,线程获得互斥锁的所有权。当该线程释放互斥锁时, 该互斥锁处于开锁状态,线程失去该互斥锁的所有权。也就是说,同时只有一个线程能获取互斥锁,特别地,持有该互斥锁的线程能够再次获得这个锁而不被阻塞, 这就是互斥锁的递归访问。互斥锁多用于保护临界资源。
?
死锁就是自己把自己阻塞了,就相当于自己把自己锁在门外,钥匙在屋里。还有一种死锁的的情况是, 两个线程相互阻塞,就好比你家的钥匙在你朋友家,你朋友家的钥匙在你家,然后你们都进不去。想要避免死锁,最好遵循以下的规则:
对共享资源操作前一定要获得锁。
完成操作以后一定要释放锁。
尽量短时间地占用锁。
如果有多个锁,如获得顺序是ABC连环扣,释放顺序也应该是ABC。
在使用互斥锁前需要初始化一个互斥锁,而在POSIX标准中支持互斥锁静态初始化和动态初始化两种方式, 如果是静态初始化的可以通过以下代码实现(选择其中一句即可):
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t recmutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
pthread_mutex_t errchkmutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;
pthread_mutex_t是互斥锁的结构体,其实就是定义一个互斥锁结构,并且将其赋值,代表不同的互斥锁, 这3种锁的区别主要在于其他未占有互斥锁的线程在获取互斥锁时是否需要阻塞等待:
PTHREAD_MUTEX_INITIALIZER:表示默认的互斥锁,即快速互斥锁。互斥锁被线程1持有时,此时互斥锁处于闭锁状态, 当线程2尝试获取互斥锁,那么线程2将会阻塞直至持有互斥锁的线程1解锁为止。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:递归互斥锁。互斥锁被线程1持有时,线程2尝试获取互斥锁, 将无法获取成功,并且阻塞等待,而如果是线程1尝试再次获取互斥锁时,将获取成功,并且持有互斥锁的次数加1。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:检错互斥锁。这是快速互斥锁的非阻塞版本,它会立即返回一个错误代码(线程不会阻塞)。
互斥锁动态初始化可以调用pthread_mutex_init()函数,该函数原型如下:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
pthread_mutex_init()函数是以动态方式初始化互斥锁的,参数说明如下:
mutex则是初始化互斥锁结构的指针。
mutexattr是属性参数,它允许我们设置互斥锁的属性,从而属性控制着互斥锁的行为,如果参数mutexattr为NULL, 则使用默认的互斥锁属性,默认属性为快速互斥锁。
线程对互斥锁的所有权是独占的,任意时刻互斥锁只能被一个线程持有,如果互斥锁处于开锁状态, 那么获取该互斥锁的线程将成功获得该互斥锁,并拥有互斥锁的所有权; 而如果互斥锁处于闭锁状态,则根据互斥锁的类型做对应的处理,默认情况下是快速互斥锁, 获取该互斥锁的线程将无法获得互斥锁,线程将被阻塞,直到互斥锁被释放,当然,如果是同一个线程重复获取互斥锁,也会导致死锁结果。获取互斥锁有2个函数,mutex参数指定了要操作的互斥锁:
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
pthread_mutex_lock()函数获得访问临界资源的权限,如果已经有其他线程锁住互斥锁,那么该函数会是线程阻塞指定该互斥锁解锁为止。
pthread_mutex_trylock()是pthread_mutex_lock()函数的非阻塞版本,使用它不会阻塞当前线程,如果互斥锁已被占用,它会理解返回一个EBUSY错误。
访问完共享资源后,一定要通过pthread_mutex_unlock()函数释放占用的互斥锁,以便系统其他线程有机会获取互斥锁,访问该资源。
简单说就是,互斥锁的使用流程应该是:
线程获取互斥锁。
然后访问共享资源。
最后释放互斥锁。
pthread_mutex_destroy()函数用于销毁一个互斥锁,当互斥锁不再使用时,可以用它来销毁,mutex参数指定了要销毁的互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define THREAD_NUMBER 3 /* 线程数 */
pthread_mutex_t mutex;
void *thread_func(void *arg)
{
int num = (int)arg;
int res;
/* 互斥锁上锁 */
res = pthread_mutex_lock(&mutex);
if (res)
{ /*获取失败*/
printf("Thread %d lock failed\n", num);
/* 互斥锁解锁 */
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
printf("Thread %d is hold mutex\n", num);
/*睡眠一定时间*/
sleep(2);
printf("Thread %d freed mutex\n\n", num);
/* 互斥锁解锁 */
pthread_mutex_unlock(&mutex);
pthread_exit(NULL);
}
int main(void)
{
pthread_t thread[THREAD_NUMBER];
int num = 0, res;
/* 互斥锁初始化 */
pthread_mutex_init(&mutex, NULL);
for (num = 0; num < THREAD_NUMBER; num++)
{
/*创建线程*/
res = pthread_create(&thread[num], NULL, thread_func, (void*)num);
if (res != 0)
{
printf("Create thread %d failed\n", num);
exit(res);
}
}
for (num = 0; num < THREAD_NUMBER; num++)
{
/*等待线程结束*/
pthread_join(thread[num], NULL);
}
/*销毁互斥锁*/
pthread_mutex_destroy(&mutex);
return 0;
}
系统创建3个线程,假设这3个线程中有临界资源被访问,那么我们希望这3个线程按顺序且不能同时去访问这个临界资源(假设临界资源是调用sleep()函数),所以我们可以使用互斥锁去限制能访问的线程,获取到互斥锁的线程可以访问临界资源。从实验结果可以看到,线程访问临界资源的顺序每次执行顺序都是不一样的,并且临界区相同时间只允许一个线程访问。