互斥的理解
一句话理解互斥: 等我用完厕所,你再用厕所。
什么是互斥?你我早起都要用厕所,谁先抢到谁先用,中途不被打扰。
伪代码如下:
void 抢厕所(void)
{
if (有人在用) 我眯一会;
用厕所;
喂,醒醒,有人要用厕所吗;
}
假设有 A、 B 两人早起抢厕所, A 先行一步占用了; B 慢了一步,于是就眯一会;当 A 用完后叫醒 B, B也就愉快地上厕所了。
在这个过程中, A、 B 是互斥地访问“厕所”,“厕所”被称之为临界资源。我们使用了“休眠-唤醒”的同步机制实现了“临界资源”的“互斥访问”。
总结:互斥锁是为了防止竞争共享资源,只有在持有锁的线程将锁解锁释放后,其它线程才能进行抢锁加锁操作。
使用场景
情景一:两个线程都在使用同一个全局变量。
例如:执行下面代码,最后的number值会是多少?21或22?其实不然
int number = 10;
void p(void)
{
number*=2;
printf("%d\n",number);
}
void q(void)
{
number+=1;
printf("%d\n",number);
}
int main(void)
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,(void*)(&p),NULL);
pthread_create(&tid2,NULL,(void*)(&q),NULL);
pthread_join(tid1);
pthread_join(tid2);
}
分析:下图表示了两个线程对全局变量操作的一种情况,线程1和2都拿到number的初始值,线程1操作后将11放回变量空间,但是线程2不久后将20也放回变量空间将11覆盖
使用互斥锁,即可解决这个问题。
使用方法
头文件:#include <pthread.h>
锁类型结构:pthread_mutex_t
创建互斥锁:pthread_mutex_init()
加锁:pthread_mutex_lock()
解锁:pthread_mutex_unlock()
测试加锁:pthread_mutex_trylock();//与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
加锁示例代码:
int number = 10;
pthread_mutex_t mutex; /*互斥量 */
void p(void)
{
pthread_mutex_lock(&mutex); /*获取互斥锁*/
number*=2;
printf("%d\n",number);
pthread_mutex_unlock(&mutex); /*释放互斥锁*/
}
void q(void)
{
pthread_mutex_lock(&mutex); /*获取互斥锁*/
number+=1;
printf("%d\n",number);
pthread_mutex_unlock(&mutex); /*释放互斥锁*/
}
int main(void)
{
pthread_t tid1,tid2;
pthread_mutex_init (&mutex, NULL);
pthread_create(&tid1,NULL,(void*)(&p),NULL);
pthread_create(&tid2,NULL,(void*)(&q),NULL);
pthread_join(tid1);
pthread_join(tid2);
}
使用原则
- 对共享资源操作前一定要获得锁。
- 完成操作以后一定要释放锁。
- 尽量短时间地占用锁。
- 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
- 线程错误返回时应该释放它所获得的锁。
互斥失败的例子
static int valid = 1;
static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)
{
if (!valid)
{
return -EBUSY;
}
else
{
valid = 0;
}
return 0; //成功
}
static int gpio_key_drv_close (struct inode *node, struct file *file)
{
valid = 1;
return 0;
}
上面示例中,通过变量valid来实现互斥,很大概率没有问题,但是并无万无一失。
分析:
程序A调用驱动程序时,它可能被程序B打断,程序B也去调用这个驱动程序。下图是一个例子,程序A在调用驱动程序的中途被程序B抢占了CPU资源:
程序A执行到第11行之前,被程序B抢占了,这时valid尚未被改成0;程序B调用gpio_key_drv_open时,发现valid等于1,所以成功返回0;当程序A继续从第11行执行时,它最终也成功返回0;这样程序A、B都成功打开了驱动程序。