关于多线程的问题.

有很多书上都说,在使用临界,公共资源的时候,一定要注意加锁.不要让多线程环境打乱整个程序的运行.
我觉得这种说法太书面了.
没有具体说清楚到底是怎么回事.

由于一般情况下,操作系统都是分时的.
一个进程里的线程可能载着一段代码(本质就是一个函数),获取cpu资源,然后执行.
可能这段代码有100行,这个线程A执行到45行的时候,CPU时间片到了.
它得释放CPU资源,以供下一个线程使用.
但有一个特别重要的步骤,那就是cpu(还是别的什么东西,好像叫进程计数器),会记录当前线程A的一些"现场环境".比如前42行代码的一些数值状态之类的.
然后,线程B进入载着同样的这100行代码进入,被调度获取到cpu资源进行执行.
由于代码本身是公共的, 那线程B载着的这段代码的前42行是不是线程A之前改变的那前42行?
也许对于B来说,可能不是一段"崭新"的代码,从第一行开始一直到最后.它处理的是一段"二手代码".

但好像,这个对A线程的第二次进入,好像没有影响.及时B线程载着二手代码执行到了55行的时候释放了cpu资源.
因为A在之前释放cpu资源之前,就记录了当时的"现场环境".所以A在获取cpu执行之前,首先要恢复当时的情景.然后再接着执行.
所以A把情景恢复到了42行,那后面的(55-42) 13行代码对于A线程来说是"全新的"还是被B线程修改过的呢?
上面写的是45行,下面写成42行了......

这个嘛,我们说浅显点。

所有的资源都是映射于内存地址中的指针管理的。编译过的代码在被托管环境下运行时,其所有*可变的*资源都是被严格管理且单独保存的。以你的例子说,即使我把A线程开10遍让它们一起跑,实际上它们执行的*机器码*都是一致的,区别在于资源各是各的,有些在内存堆着,有些被CPU抽去寄存器,有些丢在文件缓存等等(公共资源不算)。不存在谁执行了二手代码这种说法,而且线程状态的保存也远非CPU调度、状态恢复这么简单。从表象上看,其实CPU就是一忙来忙去的苦主,根据编译好的代码去解释执行,跑东跑西取资源算资源写资源,它没什么东西能自己擅作主张的,一切都计划好了。

为什么常说要注意加锁、加互斥体、用原子操作,这个我想你也清楚。不管你的A线程现在跑到哪儿了,B线程在意外时间意外修改了大家都能访问到的公共资源,这对A是一种灾难,轻点无非是A根本无法预测该资源的值,重点就会发生抢占甚至死锁。

个人拙见,看看即可。

-------------------------------------------------------------------------------------------------------------------------
刚看到你在一楼的追问。
加锁是保护*可能*遭到意外访问的共有资源的一种手段;
每个线程体都有自己独占的一块地盘(内存),当你在写Thread thd=new Thread(....)时,系统已经向其分配了初始的内存空间,用来保存A线程自己的一些信息(比如线程的当前状态啊等等),当然,很多隐藏的东西是框架管理的我们看不到。当你又开出线程B时,一样的它自己也有自己的空间。
公共资源本质上就是在内存里放着,任谁只要知道内存首地址以及里面存了什么类型的东西都可以读写。加锁,是额外的要求访问前必须过的了独占检查这一关。为什么说不要滥用公共资源呢,因为你不锁,不知道什么时候就被谁改了;你锁,每次访问前都是要消耗系统资源额外检查的。
当然锁有很多种,有的允许我写大家同时读,有的是独占连读都没门,
还有更高效的直接在寄存器中运算的原子操作,连锁都不用了但一样能实现一人“瞬间”独占的效果。追问

"实际上它们执行的*机器码*都是一致的,区别在于资源各是各的":

这里的机器码指的是编译后的,线程执行的代码吗?
资源是线程间执行的 代码中间状态吗?

追答

执行过程大致是这样的:

你写好了一个程序,编译,生成了可执行文件;(实际的exe就是二进制可执行文件)
好,我们双击执行;

.NET框架被先加载,然后它把exe执行需要的一切东西都准备好丢仅内存,当然主要的内容就是可执行的机器码(假定入口放在地址0x1000了);

CPU开始按计划忙活,它从0x1000开始执行机器代码,形如:从地址a取一个整数,从地址b取一个整数,通通送进寄存器相加,然后存到地址c;

我们的A线程开始执行了(入口在0x2000)!CPU从0x2000开始一点一点的执行,执行到0x2020时发现B线程也该滋润一下;

CPU又开始返回入口0x2000(注意地址)往前执行,只不过上面举例的abc地址可是AB线程各有一套的(私有变量),我们就说B的三个变量地址是a'b'c'吧;

CPU又该去继续执行A线程了,诶,上次执行到哪儿了?它问计数器(其实是内存中管理着的),计数器说”你上次跑到0x2020了!“CPU又屁颠颠的按照指示,屁颠颠的跑去0x2020执行;

呀,人家A线程又要求a和b的积,真讨厌,我算;

刚算完a*b,还没来得急往c地址存呢,B线程又喊饿了……

CPU急了,尼玛的我往哪里放啊,算了先问问库管,库管说”这好办,我再给A两个平米“,CPU跑去把结果丢在新内存;

返回B,拿地址,算算术,存;
返回A,拿地址,算算术,存……………………如此到海枯石烂程序结束。

执行的机器码总是同一段的,有少数情况会多复制一份放在内存;
各线程有各线程的内存,我得说线程的墓碑状态存内存里哪儿了这我还真不知道,兴许也有一部分丢二级缓存,但是一切都有运行时在管理,它会告诉CPU去哪儿找回A或者B上次的状态值的;
A的私有变量就是A的,B的就是B的,在内存空间的地址都不一样,这个谁也摸不着谁。

温馨提示:答案为网友推荐,仅供参考
第1个回答  2014-03-24
举个简单的例子吧
假如你的银行卡里边有1000块钱
你在外面消费刷卡的同时,别人往你账号上同时打钱
如果银行的系统没有一个锁机制的话,而消费和存储又同时发生的话,那么你的账号余额就乱了。
如果你消费了500,别人给你汇款了500,没有锁而二者又同时发生(虽然这种概率极低,但不能排除),那么很有可能你的账号余额是1500或者500,而实际上你的账号应该还是1000.

刷卡和存储可以看成两个线程,他们都在竞争你银行卡余额这个共同的资源,你的账号余额是1500还是500,取决于二者谁先竞争到了你账号卡里的余额数据。

所以这样的情况必须要加锁的,消费和存储同一时候只能有一个对银行卡余额进行操作。追问

你这个我的理解是,锁好像是锁定公共资源的.
就像你说的那个例子.
锁只是锁定我银行卡里的钱,但是银行卡里的钱是转入还是转出.这个锁不关心.
只要锁定了我银行卡里的钱,那么同一时间,只能有转入或者转出一种动作发生.,

追答

锁其实就是要锁定这种可能出现资源竞争情况的
要不然锁就没意义了
而且万不得已一般情况下不会去用锁
本来多线程的宗旨是并发处理以提高效率
但是用了锁之后,这个地方同一时间只允许一个线程访问
如果被锁定的这个操作不耗时还好点,一旦耗时比较严重的话,又跟单线程差不多了
所以用到锁的地方都是有可能因为资源竞争导致系统不正常才会用到的
而且还有其他更好的方案来代替锁机制
这种机制只是最直观、最原始的方式

第2个回答  2014-03-24
程序执行没有哪种语言会改程序员的代码,你说的应该是你定义的变量,这要看你定义时的作用范围,比如全局变量,线程就可以通过它实现通信,a改了b就会用到新数据,b改了a就会用到新数据,谁也不会复原,这些是存储而不是cpu的事情,同时存储这个全局变量的区域就是公共资源了,不锁住的话,a改了这个变量之后但还没执行完就被b改了,对a来说肯定是不行的,这样子逻辑就混乱了。追问

你的意思是:加锁仅仅是为了锁住全局变量吗和那种公共资源吗?
那函数体内的执行变量状态不存储吗?或者它们是单独存储的?

在每次获得cpu资源的时候,都会恢复自身的执行状态?
锁,仅仅是为了锁定公共资源?

追答

我对程序执行时,操作系统是怎么处理未执行完的函数不清楚,而且操作系统怎么处理和多线程这是两个问题,我觉得学习的时候应该先分开学习。锁的是资源,是不是公共的只对程序员有意义,狭义上来讲加锁就是你说的那个含义,这是程序员对这部分资源的特殊保护,而你说的函数执行的事情其实是操作系统来管。我记得变量什么的是放到寄存器里的,没处理完的资源肯定不会释放,这种调度是操作系统处理,看你的说法对语句主语不太在意,这对很注意分工的计算机是不利于学习的。我知道的有限,希望能帮到你。

追问

谢谢你的建议.

相似回答