Python多线程同步实例分析

进程之间通信与线程同步是一个历久弥新的话题,对编程稍有了解应该都知道,但是细说又说不清。一方面除了工作中可能用的比较少,另一方面就是这些概念牵涉到的东西比较多,而且相对较深。网络编程,服务端编程,并发应用等都会涉及到。其开发和调试过程都不直观。由于同步通信机制的原理都是想通的,本文希通过望借助Python实例来将抽象概念具体化。

阅读之前可以参考:Python多线程与多进程及其区别,了解一下线程和进程的创建。

python多线程同步

python中提供两个标准库thread和threading用于对线程的支持,python3中已放弃对前者的支持,后者是一种更高层次封装的线程库,接下来均以后者为例。

同步与互斥

相信多数学过操作系统的人,都被这两个概念弄混过,什么互斥是特殊的同步,同步是多线程或多进程协同完成某一任务过程中在一些关键节点上进行的协同的关系等等。

其实这两个概念都是围绕着一个协同关系来进行的,可以通过一个具体的例子来清晰的表达这两个概念:

有两个线程,分别叫做线程A和线程B,其中线程A用来写一个变量,线程B读取线程A写的变量,而且线程A先写变量,然后线程B才能读这个变量,那么线程A和B之间就是一种同步关系

===== 同步关系 =====
Thread A: write(share_data) V(S) # 释放资源 Thread B: P(S) # 获取资源 read(share_data)

如果又来一个线程C,也要写这个变量,那么线程A和C之间就是一种互斥关系,因为同时只能由一个线程写该变量;

===== 互斥关系 ===== Thread A: Lock.acquire(); # 获得锁 write(share_data) Lock.release() # 释放锁 Thread C: Lock.acquire(); # 获得锁 write(share_data) Lock.release() # 释放锁

线程同步

主线程和其创建的线程之间各自执行自己的代码直到结束。接下来看一下python线程之间同步问题.

交替执行的线程安全吗?

先来看一下下面的这个例子:

share_data = 0 def tstart(arg): time.sleep(0.1) global share_data for i in xrange(1000): share_data += 1 if __name__ == '__main__': t1 = threading.Thread(target = tstart, args = ('',)) t2 = threading.Thread(target = tstart, args = ('',)) t1.start() t2.start() t1.join() t2.join() print 'share_data result:', share_data

上面这段代码执行结果share_data多数情况下会小于2000,上一篇文章介绍过,python解释器CPython中引入了一个全局解释器锁(GIL),也就是任一时刻都只有一个线程在执行,但是这里还会出问题,为什么?

根本原因在于对share_data的写不是原子操作,线程在写的过程中被打断,然后切换线程执行,回来时会继续执行被打断的写操作,不过可能覆盖掉这段时间另一个线程写的结果。

下面是一种可能的运算过程:

Python多线程同步实例分析

实际计算过程可能比上面描述的更复杂,可以从单个线程的角度来理解,如果不加同步措施,对于单个线程而言其完全感知不到其他线程的存在,读取数据、计算、写回数据。

如果在读取数据之后,计算过程或者写回数据前被打断,当再次执行时,即使内存中的share_data已经发生了变化,但是该进程还是会从中断的地方继续执行,并将计算结果覆盖掉当前的share_data的值;

这就是为什么每一时刻只有一个线程在执行,但是结果还是错的原因。可以想象如果多个线程并行执行,不加同步措施,那么计算过程会更加混乱。

感兴趣的话可以使用一个全局列表的res,记录下每个线程写share_data的过程,可以比较直观的看到写的过程:

Python多线程同步实例分析

share_data = 0 res = [] def tstart(arg): time.sleep(0.1) global share_data for i in xrange(1000): res.append((arg, share_data)) share_data += 1 if __name__ == '__main__': t1 = threading.Thread(target = tstart, args = ('1',)) t2 = threading.Thread(target = tstart, args = ('2',)) t1.start() t2.start() t1.join() t2.join() print res, len(res) print 'share_data result:', share_data

View Code

下面是一种可能的结果,可以看到两个线程对share_data的确进行了2000次加一操作,但是结果却不是2000.

Python多线程同步实例分析

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/bab83c7945682ba42daa254705097e2d.html