多线程之mutex、semaphore区别

多线程

在讲多线程同步关键词之前,先说一下单道程序。在单道程序中,每时每刻只有一个程序运行,该程序独自占据cpu。只有当该程序完成之后才释放其占据的所有资源。因为只有一个程序在运行,占据cpu和访问内存,没有其他程序干扰。所以所有操作肯定是顺序完成的,即满足顺序行、封闭性、安全性,这个时候不必担心各种并发问题的出现。但是这只有一个程序在运行会对资源产生很大的浪费。因为当这个唯一的程序在等待其他资源的时候,该程序就无法执行,那么cpu在这个时候也必须等待该程序一直到该程序可以执行。这个时候cpu就什么事情也不能做,只能空等,导致资源浪费。引入多道程序的目的是解决单道程序中cpu等待的场景。

为了解决上述问题引入了多道程序,和单道程序一样,同一时刻也是只能一个程序占据一颗cpu(多核CPU)。但是和单道程序不同的是,当该程序等待其他资源的时候,它就放弃cpu或者当一个设定的时间到了放弃cpu,让其他程序拥有cpu。该程序不可能一直占据,如此这就避免了cpu空等的情况,让cpu始终有程序运行。因为程序不是一直从开始执行到结束,而是中间走走停停,那就并发问题就会出现。因为当他停止的时候,它所拥有的资源或者它所需要的资源可能会被其他程序修改,这就影响程序运行的正确性。

程序的可重入

但是也并不是说所有的多线程同事运行就一定会导致多线程问题的出现。下面就简单介绍一下可充入与线程安全两个知识点。

  所谓的可重入是在单道程序时代提出来的,一个程序因为操作系统中断被挂起,当再次进入程序执行的时候可以依旧正确运行。当我们表达一个函数可重入的时候必须部包含静态变量和全局变量,不能调用其他不可重入函数,只处理传入的数据。从这里也可以看出,严格函数式的编程中的函数肯定都是可重入的。

  线程安全性则是指在多线程环境下的一种现象。线程安全与可重入有一定的联系,但也并不是严格的因果关系。可重入肯定是线程安全,因为没有公共数据的修改;但是不可重入通过一定的同步机制也可以变成线程安全的。

同步机制

对那些不可重入的多线程,我们就必须让多个线程进行同步操作。

所谓同步是指的一个线程在访问数据资源没结束之前,其他线程不可以随意访问并修改数据。从这里可以看出,同步是我们在处理多线程问题的目的,为了达到同步目的我们有多种实现机制。下面就开始简单介绍几种同步机制,通过使用同步机制保证多线程安全。

其中常用的同步机制主要包括信号量互斥锁临界区等几种。在此基础上,有些语言会按照这些互斥机制研发不同的锁机制,比如自旋锁,可重入锁等,但是底层的机制也是离不开这几种原理。

1:semaphore(信号量)范围比较广,semaphore可能会有多个属性值。比如常见的生产者和消费者问题,就是多元信号量的一种。生产者可以生产多个元素,消费者可以消费的元素必须小于生产者的生产元素个数。从此也可以看出,semaphore是允许多个线程进入,访问互斥资源。除了多元信号量之外,还存在一种二元信号量。即只存在是与否,0与1两种状态。

2:mutex(互斥量)也是一种二元的锁机制,只有是(1)和否(0)的两个值,和二元信号量比较相似。但是它和二元信号量不同的是,占有和释放必须是同一个线程。比如互斥量M被线程A占有,那么释放的时候肯定也是A线程释放的。二元信号量则不必如此,一个二元信号量的占有和释放可以是不同线程。相应的内容也可以移步看一下wiki解释--Synchronization 。mutex是可以用于进程也可以用于线程的同步机制,见wiki(refers to the problem of ensuring that no two processes or threads (henceforth referred to only as processes) are in their critical section at the same time.)

3:critical section(临界区)是一种比互斥量更严格的互斥访问机制。只允许一个进程访问,不可能两个进程进行竞争。一个进程创建了临界区以后,其他进程是不可能获取到该临界区的进入权利的,但是允许进程内的多个线程竞争。在某些情况下我们可以使用mutex机制来保护critical section。我们可以考虑java中临界区,java是一个进程下的多线程。

4:对于lock,其实它是一种同步机制的统称,即锁机制。通过锁机制实现线程之间的同步,但是锁机制的实现是有很多种。除了上述的几种还有其他比如条件变量等。在其他语言中也有其他锁的机制,比如.net中的monitor,其实monitor是lock的一种实现。不同语言上对于锁机制的实现就可能有些许差别。虽然有时候说的是一种锁,但是其底层的具体实现机制就有可能是临界区或者信号量机制等。

在锁之中有种特殊锁,读写锁。简单理解是在读的时候可以共享读,但不可以写;在写的时候只能独占式的写。像这种锁,在一些数据库中被广泛使用。通过读写锁的使用,让数据库的读的性能得到提升。

问题

虽然有各种锁的出现,让多线程问题得到一部分的解决,但是仅仅通过语言或者系统层面给出解决方式是不够的。因为在编译器优化、在CPU都会让高级语言编译成低级语言,在调用指令的时候出现乱序的问题。在这个时候虽然程序中采用了锁的机制,但是依旧会出现多线程执行乱序问题,导致程序执行结果失败。


你可能感兴趣的:(多线程)