当前位置: 首页 > >

Python线程中的互斥锁和ThreadLocal

发布时间:

昨天说了用threading模块创建多线程,但是多线程的开发可能遇到很多问题,由于全局变量的共享,就会引发数据混乱,往往达不到我们的需求。下面我们举一个例子来说明一下这个问题。假设一个全局变量num=0,我们创建两个线程,每个线程都进行num+1循环。最终得到的结果应该得到20,但是由于CPU的调度,就有可能会出现两个线程同时对num进行修改,最后得到的结果有可能不是num=20。这样就达不到我们的需求了。(这只是举个例子循环10次+1对于cpu就是微不足道的运算,但是我们需要进行100万次加一呢,这样结果就会相差很远)
来个简单的代码图,给大家看看这个问题吧,更加直观。


看到结果并不是我们想象中的200万,而且差的还有点多,这就是线程的一个问题,会发生数据混乱。这种现象称为线程不安全
这个时候我们就要引入同步的概念了
什么是同步?同步就是协同步调,按照预定的先后次序进行运行。我们如何解决这个问题呢?就需要使用互斥锁(同步锁)


互斥锁:互斥锁为资源引入一个状态:锁定/非锁定。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。


threading模块中定义了Lock类,可以方便的处理锁定:
创建锁:mutex = threading.Lock()
锁定:mutex.acquire([blocking])
释放:mutex.release()
其中,锁定方法acquire可以有一个blocking参数。
如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为止(如果没有指定,那么默认为True)
如果设定blocking为False,则当前线程不会堵塞。
就还是上面的代码,我给加上互斥锁,我们大家再看看运行结果。


我们来对比一下结果,加上互斥锁之后,结果就是200万,满足了我们的需求。


使用互斥锁的优缺点
优点:确保了某段关键代码只能由一个线程从头到尾完整的执行。
缺点:1:阻止了多线程并发执行,包含锁的某段代码只能以单线程模式执行,效率就大大降低了。
2:由于可以存在多个锁,不同线程拥有不同的锁,并且试图获得对方的锁,这样就可能造成死锁。


死锁这个非常好理解,大家自己把代码跑一遍就知道是什么意思了,为什么会发生这种情况,所以我们在创建多线程执行代码时就需要多考虑考虑,会不会出现死锁的情况。一定要把这种情况扼杀在摇篮里。
下面我们说一下ThreadLock
在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
问题就来了,由于局部变量不是共享的,使用时传递起来会非常的麻烦。所以ThreadLock就来帮我们解决这个问题了。
先给大家跑一段代码,大家再进行理解一下。

全局变量localschool就是一个ThreadLocal对象,
每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如localschool.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
全局变量localschool就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。


这下进程和线程总结的都差不多了,应该还缺少一些知识点,比如进程池(pool)和进程之间的听信(Queue)这些都是模块里为我们提供的方法,在Windows里我们可以cmd然后使用help来查看使用方法,注释非常详细,这样还可以培养我们的学*能力,如果是Linux系统中,我们可以打开终端使用man或者help都可以查看使用方法,进程和线程到这里就结束了。


程序员的练成是充满枯燥的,除了理解的知识就是代码量,想要得到就要付出不是吗?所以加油吧,欢迎大家和我这个初学者一起来讨论问题,python的基础知识点和语法,还有高级编程,我基本理解的差不多了,缺少的就是项目的经验,时间是检验真理的标准



友情链接: