互斥与同步

170 阅读3分钟
原文链接: mp.weixin.qq.com

今天来简单地学习Linux中的编发编程,介绍一下互斥和线程同步。

竞争条件

  • 当两个或多个进程试图在同一时刻访问共享内存,或读写某 些共享数据,而最后的结果取决于线程执行的顺序(线程运行时序),就称为竞争条件

  • 如果没有同步机制,多道程序设计很容易因为竞争条件而出现不是我们所期待或者是错误的结果

临界区

  • 对共享变量,共享内存,共享资源进行访问的程序片度叫做临界区

互斥

在某些提供test-and-set指令的机器可以用于解决临界区互斥的问题,这个指令能检查并设置内存的值,而不会被打断。工作原理大致是这样的:

boolean TestAndSet(boolean *lock){

boolean rv = *lock;

*lock = TRUE;

return rv;

}

注意:这个函数中三条指令是原子执行的,也就是说不会被打断。

使用方法:

boolean lock = false;

while(TestAndSet(&lock)){

;// 啥也不干

}

临界区;

lock = false;

其他代码;

信号量

  • 除了使用test-and-set指令来解决互斥问题,还可以使用更加强大的信号量。

如上图所示,对于一个互斥的信号量(比如互斥锁),count属性会被初始化为1,代表最多一个任务可以获得这个信号量。

当一个任务调用down(&sem)方法——这个方法会对减少count值,如果新的count值大于0(获得信号量)则会立即返回执行接下来的代码,否则,这个任务会被进入睡眠,即放到信号量的 task_list等待队列。之后,当一个已经获得信号量的任务完成之后,调用up(&sem)方法,count值就会增加,等待队列中的所有任务都会被唤醒。

从上面的描述可知,使用信号量不仅可以解决互斥的问题,还可以解决生产者-消费者同步的问题。

使用信号量解决互斥的伪代码:

sem->count=1;

down(&sem);

临界区

up(&sem);

使用信号量解决生产者-消费者同步问题伪代码:

sem_lock->count = 1; // 用于访问队列时互斥

sem_empty->count = 5; // 生产者信号量,大于0可生产

sem_full->count = 0; // 消费者信号量,大于0可消费

生产者:

while(true){

down(&sem_empty); // 如果队列中没有空余的位置,生产者只能等待

down(&sem_lock); // 加锁,准备操作队列

把新产生的内容加入队列;

up(&sem_lock); // 释放锁

up(&sem_full); // 通知消费者有内容可以消费了

}

消费者:

while(true){

down(&sem_full); // 如果队列中没有内容,消费者只能等待

down(&sem_lock); // 加锁,准备操作队列

将队列中的内容取出进行处理;

up(&sem_lock); // 释放锁

up(&sem_empty); // 通知生产者进行生产

}

  • 注意:当然对信号量的down和up操作是在内核中实现的,并且屏蔽了中断,否则count本身也会出现竞争条件。

总结

本文不但学习了test-and-set指令实现互斥,还学习了更加强大的信号量来解决 生产者-消费者同步问题。