多线程编程总结:一、认识多线程本质
在当今计算机系统中,已经大量存在多核心CPU,或者是在多核心基础上有进一步的超线程技术将虚拟CPU数量翻倍。在计算机发展之初,我们的应用程序是按照一个CPU只做一件事情来应用,也就是顺序执行。随着时间的不断变化,我们的CPU计算能力越加强大,那么我们可以使用线程技术,让每个核心都去做一件事,或者使用时间切片(time slicing)技术,让我们的CPU在各个线程中切换以同时达到一种处理多个线程任务的目标。可以同时听歌,看文档,运行时钟,挂游戏。
需要注意的是,对于时间分片技术,我们实际上是同一个核心将一个线程的运行1个时间片(time slice)的时间,然后保存状态,切换到另外一个线程去,这种切换动作被称为上下文切换。表现出来是并行运算。但是我们需要考虑到CPU不断地切换线程,实际上也会有代价,需要保存上一个线程状态,切换到下一个线程去。如果线程数目过多的情况下,就会消耗大量时间在切换线程上。这就是为什么多线程不一定会让程序更快,而需要综合衡量。
多线程并发问题:缺乏原子性、竞态条件、复杂的内存模型和死锁。
一、缺乏原子性
首先原子操作的定义是:一个原子内代码,要么处于没有操作,要么已经操作完毕,而不存在“操作中”这个中间态。核心是它是整体的,不可分割。如果我们的多线程代码如果不是原子性的,那么这种情况下它就缺乏原子性。
二、竞态条件
竞态条件是值得我们同时开启的多个线程,它的执行顺序是根据系统判断哪个线程竞争胜利,先执行到一部分,然后发生上下文切换到另外一个另外一个线程去。而至于哪个线程在竞争中胜出不可预知,就算99.99%的时间具有正确行为,那么也有0.01%会出现另外一个线程竞争胜利。
三、复杂的内存模型
现在我们的CPU不会每次运行某个变量的时候都会去内存取出操作,而是将这个变量缓存在CPU的高速缓存中,这个缓存会定时和主内存进行同步。意味着在多核心CPU中处理不同线程时,我们的线程处理的是各自CPU核心的高速缓存中的变量,实际上是2个不同的变量。那么当我们多线程对该变量进行更新时就不是准确的。
四、锁定造成死锁
C#中使用Lock语句或者Monitor.Enter() Monitor.Exit()将一段代码作为原子操作,对Lock住的该对象,系统会判断只允许一个线程访问该段Lock住代码,其他线程挂起等待。如果被Lock住的对象发生改变,其他线程访问过来的时候,系统会认为不是同一段Lock代码,就会允许那个代码访问,这就是Lock失效了。
锁本身也会出问题,例如此处有2个线程分别是A和B线程, 同时有2个锁分别是C和D,那么A线程在C锁获得之后请求D锁,而B线程在获得D锁之后请求C锁,就会造成互相等待对方释放,这就是死锁的由来。