概述
python对多线程的支持
先看一个概念:
官方描述:In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)
由于GIL的存在,python其实无法利用多处理器的优势,任意时刻只会有一个线程运行在解释器中,也就是大计算量的程序在python中通过多线程处理其实不见得会变快。但是IO密集型程序可以很好地利用多线程,比如用python开发一个rest客户端程序,如果单线程实现,假如发送一个http请求服务器端需要耗费5s来处理,串行发送1000个就需要5000s左右;但是开1000个线程,就可以同时发送1000个请求,一起等待相应,基本10s之内就能完成这个过程。
Python中多线程相关的模块包括:thread,threading,Queue。
thread:多线程的底层支持模块,一般不建议使用【本文暂不涉及】
threading:对thread进行了封装,将一些线程的操作对象化
Queue:实现了多生产者(Producer)、多消费者(Consumer)的队列
开始使用多线程
入门例子
python的threading库可以实现在单独的线程中执行任意的python可调用对象,我们通过创建Thread类的实例,然后提供其需要被单独线程执行的可调用对象,就完成目的了,看一个入门例子:
创建一个线程实例需要传递给它目标函数的引用,参数元组,然后调用start方法就会开始这个线程的运行。
通过Thread类可以有多种方法创建线程:
用一个函数作为参数实例化一个Thread类,多线程执行这个函数(上面例子所示)
用一个可调用类作为参数实例化一个Thread类,多线程执行这个“可调用类”(和第一个本质相同)
从Thread类派生一个子类,重写run方法(比较常规的用法)
多线程中的join
观察上面输出会发现“All done!”居然在最开始输出了,我们的本意是线程执行完之后才输出“All done!”,咋办呢?看下面一段代码:
这里介绍一下这个join,文档中有如下一句话:
"Wait until the thread terminates"
也就是说要等待这个线程执行完毕才开始后续操作,这样就实现了等待子线程完成再进行其他操作的目的。
该函数定义是def join(self, timeout=None): ...
也就是说还可以设置timeout参数,避免子线程出问题一直不结束的情况下父线程无限等待的问题
用一个可调用类作为参数实例化一个Thread类
先看下面代码:
如上,其实这里很好理解,主要注意的一个知识点是类的“call()”方法,这里的target is a callable object, 是一个可调用对象,MyThread(show, ('wing',))实例化了一个类,得到的对象就是这样一个可调用对象,和函数名对应,真正调用的时候就是执行了__call__()方法,这里的__call__()只是简单地执行初始化时传递过来的函数,类来实现相比于函数要灵活很多。
从Thread类派生一个子类,重写run方法(推荐的方法)
代码:
这里继承了threading模块的Thread类,重写了init和run方法,通过这种方式来实现多线程执行的效果。
多线程处理的返回值问题
上面的show函数只是简单的打印操作,但是如果需要多线程处理的函数如下:
这个时候需要记录函数执行的结果,在上面的实现中并不能达到这样的效果,这个时候我们可以稍微修改一下MyThread类,如下:
python线程同步机制
最后的结果是0,准确说多次执行发现结果是0,至于0是不是唯一结果,这里先不下结论,如果我们尝试把sleep(1)这一句注释放开,就会发现结果基本变成了-1
其实这里的sleep表示的只是对count操作之前的过程可能会耗时较长,这个时候count可能已经被改变了,而我们的本意是count大于0时才执行一次操作,本线程做这个处理的时候,不希望其他线程同时操作count的值。
再看下面一段代码:
如上,通过加锁实现了线程同步,这里的锁释放还可以用更优雅的方式实现,如下:
领取专属 10元无门槛券
私享最新 技术干货