互斥锁(mutex)
互斥体
- 互斥体实现了“互相排斥”(mutual exclusion)同步的简单形式(所以名为互斥体(mutex))。互斥体禁止多个线程同时进入受保护的代码“临界区”(critical section)。因此,在任意时刻,只有一个线程被允许进入这样的代码保护区。
- 任何线程在进入临界区之前,必须获取(acquire)与此区域相关联的互斥体的所有权。如果已有另一线程拥有了临界区的互斥体,其他线程就不能再进入其中。这些线程必须等待,直到当前的属主线程释放(release)该互斥体。
- 什么时候需要使用互斥体呢?互斥体用于保护共享的易变代码,也就是,全局或静态数据。这样的数据必须通过互斥体进行保护,以防止它们在多个线程同时访问时损坏
互斥锁
1、定义
- 为了保证共享数据操作的完整性。每个对象都对应于一个可称为" 互斥锁" 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 使用互斥锁(互斥)可以使线程按顺序执行。通常,互斥锁通过确保一次只有一个线程执行代码的临界段来同步多个线程。互斥锁还可以保护单线程代码
一段需要互斥执行的代码称为临界区。线程在进入临界区之前,首先尝试加锁lock(),如果成功,则进入临界区,此时称这个线程持有锁;否则,就等待,直到持有锁的线程解锁;持有锁的线程执行完临界区的代码后,执行解锁unlock()。
2、属性
- 原子性:把一个互斥锁定义为一个原子操作,这意味着操作系统保证了如果一个线程锁定了互斥锁,则没有其他线程可以在同一时间成功锁定这个互斥量。
- 唯一性:如果一个线程锁定一个互斥量,在它接触锁定之前,没有其他线程可以锁定这个互斥量。
- 非繁忙等待:如果一个线程已经锁定了一个互斥锁,第二个线程又试图去锁定这个互斥锁,则第二个线程将被挂起(不占用CPU资源),直到第一个线程解锁,第二个线程则被唤醒并继续执行,同时锁定这个互斥量
3、使用流程(相关API函数)
- 互斥锁初始化和销毁的函数原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *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_init()
#include <pthread.h>//头文件
1、定义
互斥锁的初始化
2、函数原型:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
-
pthread_mutex_t: 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。
-
mutex:是指向要初始化的互斥锁的指针。
-
attr: 是指向属性对象的指针,该属性对象定义要初始化的互斥锁的属性。如果该指针为 NULL,则使用默认的属性。,默认属性为快速互斥锁
- PTHREAD_MUTEX_TIMED_NP,这是缺省值,也就是普通锁。当一个线程加锁以后,其余请求锁的线程将形成一个等待队列,并在解锁后按优先级获得锁。这种锁策略保证了资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP,嵌套锁,允许同一个线程对同一个锁成功获得多次,并通过多次unlock解锁。如果是不同线程请求,则在加锁线程解锁时重新竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP,检错锁,如果同一个线程请求同一个锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMED_NP类型动作相同。这样保证当不允许多次加锁时不出现最简单情况下的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP,适应锁,动作最简单的锁类型,仅等待解锁后重新竞争。
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的属性初始化互斥体变量mutex
3、返回值:
成功则返回0, 出错则返回错误编号.
4、初始化方式
- 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。
pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
- 动态初始化:局部变量应采用动态初始化。
pthread_mutex_init(&mutex, NULL)
pthread_mutex_destroy()
1、定义
mutex 指向要销毁的互斥锁的指针
2、函数原型
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁销毁函数在执行成功后返回 0,否则返回错误码。
pthread_mutex_lock()(阻塞)
1、定义
对共享资源的访问, 要对互斥量进行加锁, 如果互斥量已经上了锁, 调用线程会阻塞, 直到互斥量被解锁. 在完成了对共享资源的访问后, 要对互斥量进行解锁。
2、函数原型
int pthread_mutex_lock(pthread_mutex_t *mutex);
返回值: 成功则返回0, 出错则返回错误编号.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
说明: 具体说一下trylock函数, 这个函数是非阻塞调用模式, 也就是说, 如果互斥量没被锁住, trylock函数将把互斥量加锁, 并获得对共享资源的访问权限; 如果互斥量被锁住了, trylock函数将不会阻塞等待而直接返回EBUSY, 表示共享资源处于忙状态。
3、基于mutex类型的返回值
- 如果mutex 对象的type是 PTHREAD_MUTEX_NORMAL,不进行deadlock detection(死锁检测)。企图进行relock,这个mutex会导致deadlock.如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,结果是不未知的。
- 如果mutex类型是PTHREAD_MUTEX_ERRORCHECK,那么将进行错误检查。如果一个线程企图对一个已经锁住的mutex进行relock,将返回一个错误。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。
- 如果mutex类型是 PTHREAD_MUTEX_RECURSIVE,mutex会有一个锁住次数(lock count)的概念。当一个线程成功地第一次锁住一个mutex的时候,锁住次数(lock count)被设置为1,每一次一个线程unlock这个mutex的时候,锁住次数(lock count)就减1。当锁住次数(lock count)减少为0的时候,其他线程就能获得该mutex锁了。如果一个线程对未加锁的或已经unlock的mutex对象进行unlock操作,将返回一个错误。
- 如果mutex类型是 PTHREAD_MUTEX_DEFAULT,企图递归的获取这个mutex的锁的结果是不确定的。unlock一个不是被调用线程锁住的mutex的结果也是不确定的。企图unlock一个未被锁住的mutex导致不确定的结果。
pthread_mutex_unlock()
1、定义
函数释放有参数mutex指定的mutex对象的锁
2、函数原型
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值: 成功则返回0, 出错则返回错误编号.
互斥锁应用举例
由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱:
1、不使用互斥锁
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
void *tfn(void *arg)
{
for(int i =0;i < 5;i++)
{
printf("hi, ");
sleep(1);
printf(" boy \n");
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_create(&tid, NULL, tfn, NULL);
for(int i =0;i < 5;i++)
{
printf("hello, ");
sleep(1);
printf(" girl\n");
sleep(1);
}
pthread_join(tid , NULL);
return 0;
}
输出结果无序
2、使用互斥锁
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
pthread_mutex_t mutex;
void *tfn(void *arg)
{
for(int i =0;i < 5;i++)
{
pthread_mutex_lock(&mutex);
printf("hi, ");
sleep(1);
printf(" boy \n");
pthread_mutex_unlock(&mutex);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t tid;
pthread_mutex_init(&mutex, NULL);
pthread_create(&tid, NULL, tfn, NULL);
for(int i =0;i < 5;i++)
{
printf("hello, ");
sleep(1);
printf(" girl\n");
sleep(1);
}
pthread_mutex_destroy(&mutex);
pthread_join(tid , NULL);
return 0;
}
输出结果有序
3、多个线程实验
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t mutex;
int val = 0;
void *ThreadFunc(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex); /*获取互斥锁*/
int i = 0;
for(i = 0 ; i < 10000 ; i++)
{
val++;
}
printf("val = %d\n" , val);
pthread_mutex_unlock(&mutex); /*释放互斥锁*/
usleep(20);
}
return 0;
}
int main()
{
void *ResVal;
pthread_mutex_init (&mutex, NULL); /*定义*/
pthread_t thread1 , thread2;
pthread_create(&thread1 , NULL , ThreadFunc , NULL);
pthread_create(&thread2 , NULL , ThreadFunc , NULL);
pthread_join(thread1 , &ResVal);
pthread_join(thread2 , &ResVal);
}
若使用互斥锁,则val永远是以10000位单位自加,不使用互斥锁,输出结果无序,不可控。
参考
1、https://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html
2、https://www.cnblogs.com/zhanghongfeng/p/10294975.html
3、https://blog.csdn.net/qq_24373811/article/details/52371792
4、https://blog.csdn.net/qq_39736982
5、https://blog.csdn.net/wssjn1994/article/details/86064852
6、https://www.cnblogs.com/jiu0821/p/6100824.html