所谓串行,就比如咱们走路同样,一条马路,一次只能过一辆车,这样速度就会很受限制。html
理解了串行,并行就更好理解了,就是好多条路。路越多,车流量就越大。python
多线程就是并行的一种。固然,实际发生在计算机内部的时候,并不能单纯的理解为多了一条路。由于咱们的计算机一个CPU核心,同时只能处理一个任务。在CPU只有一个核心的状况下,多线程咱们就能够理解为开辟出了许多条道路,可是咱们的出口只有一个。每条路上面的车都会你争我抢谁也不让,哪条路抢到了通行权这条路上的汽车就会赶快经过,直到下条路抢到通行权,其余路上的汽车都会进入等待状态。实际发生在计算机内部的时候,线程之间的切换都是毫秒级的,因此人的没法感受出来线程之间有等待的,在人看来全部的线程都是同时运行的。使用多线程,能够大大的增长程序的性能和效率。web
# 导入Python标准库中的Thread模块 from threading import Thread # 建立一个线程 t = Thread(target=function_name, args=(function_parameter1, function_parameterN)) # 启动刚刚建立的线程 t.start()
function_name: 须要线程去执行的方法名数据库
args: 线程执行方法接收的参数,该属性是一个元组,若是只有一个参数也须要在末尾加逗号。数组
from threading import Thread # 建立一个类,必需要继承Thread class MyThread(Thread): # 继承Thread的类,须要实现run方法,线程就是从这个方法开始的 def run(self): # 具体的逻辑 function_name(self.parameter1) def __init__(self, parameter1): # 须要执行父类的初始化方法 Thread.__init__(self) # 若是有参数,能够封装在类里面 self.parameter1 = parameter1 # 若是有参数,实例化的时候须要把参数传递过去 t = MyThread(parameter1) # 一样使用start()来启动线程 t.start()
在上面的例子中,咱们的主线程不会等待子线程执行完毕再结束自身。可使用Thread类的join()方法来子线程执行完毕之后,主线程再关闭。安全
from threading import Thread class MyThread(Thread): def run(self): function_name(self.parameter1) def __init__(self, parameter1): Thread.__init__(self) self.parameter1 = parameter1 t = MyThread(parameter1) t.start() # 只须要增长一句代码 t.join()
上面的方法只有一个线程,若是有多个线程,能够把每一个线程放在一个数组中。服务器
thread_list = [] for i in range(1, 11): t = MyThread(parameter1) thread_list.append(t) t.start() # 在这里统一执行线程等待的方法 for t in thread_list: t.join()
在咱们的计算机中,须要大量用到CPU计算的事情,咱们称为CPU密集型操做。数据结构
如,咱们计算9的一亿次方,这种大型的运算,或者是进行文件格式的转换,这些都是属于CPU密集型操做。多线程
注意:上面的运算会消耗很长的计算时间,有兴趣能够从小到大慢慢尝试一下app
所谓IO密集型操做,就是涉及到大量的输入输出,好比频繁的数据库访问,频繁的web服务器访问,这种状况都属于IO密集型操做。
咱们都知道,多线程最大的一个问题就是线程之间的数据同步问题。在计算机发展过程当中,各个CPU厂商,为了提高本身的性能,引入了多核概念。可是多个核心之间若是作到数据同步让全部人都花费了不少的时间和金钱,甚至最后消耗了CPU不少的性能才得以实现。
了解Python的朋友都知道,Python默认的实现是CPython,而CPython使用的是C语言的解释器。而因为历史缘由,CPython中不幸的拥有了一个在将来很是影响Python性能的因素,那就是GIL。GIL全称Global Interpreter Lock
,又叫全局解释器锁。GIL是计算机程序设计语言解释器用于同步线程的工具,而CPython中正是支持了GIL的特性,使得Python的解释器同一时间只能有一条线程运行,一直等到这个线程执行完毕释放了全局锁之后,才能有其余的线程来执行。也就是说,CPython自己其实是一个单线程语言,甚至在多核CPU上面使用CPython的多线程反而性能不如单线程高。
人们对于GIL还存在很大的误解,GIL只存在于Python中的CPython,使用Jython或者PyPy则不存在这个问题。
- Python的线程是操做系统线程。在Linux上为pthread,在Windows上为Win thread,彻底由操做系统调度线程的执行。一个python解释器进程内有一条主线程,以及多条用户程序的执行线程。即便在多核CPU平台上,因为GIL的存在,因此禁止多线程的并行执行。
- Python解释器进程内的多线程是合做多任务方式执行。当一个线程遇到I/O任务时,将释放GIL。计算密集型(CPU-bound)的线程在执行大约100次解释器的计步(ticks)时,将释放GIL。计步(ticks)可粗略看做Python虚拟机的指令。计步实际上与时间片长度无关。能够经过sys.setcheckinterval()设置计步长度。
- 在单核CPU上,数百次的间隔检查才会致使一次线程切换。在多核CPU上,存在严重的线程颠簸(thrashing)。
咱们都知道计算机一开始只是单核的,在那个年代人们并不会想到多核这种状况,因而为了应对多线程的数据同步问题,人们发明了锁。可是若是本身来写一个锁,不只耗时耗力,并且还会隐藏许多未知的BUG等问题。因而在这样的大背景下,Python社区选择了最简单粗暴的方式,实现GIL,这样作有如下几点好处:
- 能够增长单线程程序的运行速度(再也不须要对全部数据结构分别获取或释放锁)
- 容易和大部分非线程安全的 C 库进行集成
- 容易实现(使用单独的 GIL 锁要比实现无锁,或者细粒度锁的解释器更容易)
可是令Python社区没想到的是,CPU乃至计算机发展的如此迅速,双核,四核,甚至多CPU计算机的出现,让Python在很长一段时间内背负着运行效率低下的称号。而当Python社区和众多的Python库做者回过头想修改这些问题的时候却发现,代码与代码之间紧紧的依赖于GIL,面对庞大的绕成一团的线,也只能抽丝剥茧般的慢慢剔除。
值得庆幸的是,虽然咱们不知道这一过程用了多久,可是在Python3.2中开始使用了全新的GIL,将大大的提高CPython的性能。
不少人提到Python就想到爬虫,由于爬虫在某些程度上来讲,Python的缺点彻底不存在,并且还成了优势。咱们来分析一下爬虫的运行过程:
- 发送请求
- 等待服务器响应
- 收到服务器响应数据
- 解析
咱们来看一下,以当前的计算机配置来讲,对爬虫获取到的数据来进行解析处理的话,可能只须要几毫秒甚至更短的时间就能完成。那么一个爬虫程序最影响性能的地方在哪里?
是IO操做。没错,咱们的爬虫发出请求之后要等待对方的服务器来响应,这一过程是最耗时的,有时可能会须要一两秒的时间。此时,咱们就能够在请求发送出去之后,马上释放咱们的全局锁,而后让下一个线程执行。直到某一个线程的响应回来之后消耗几毫秒处理数据,而后再次开始发送请求,而因为同一时间只有一条线程运行不须要考虑其余的问题,因此性能也会大大的提高。
及时在爬虫上使用了真正意义的多线程,无非就是在解析数据的时候多几个线程来处理罢了。那么0.2毫秒和0.02毫秒,乃至无限至今于0毫秒的时间,他们之间的区别又是什么呢?一样都是人类没法分辨出来的差距,而咱们又要对线程进行大量的安全性的处理,得不偿失。
在了解了线程之后,咱们可能须要在多个线程之间通讯。实现这一点,咱们能够声明一个全局的存储对象,全部的线程都调用这一个对象来进行数据的存和取,这样就能够作到线程间的通讯。
咱们使用传统的列表或元组都是能够的,可是列表和元组他们都是线程不安全的存储结构,咱们须要本身加锁来保证线程安全。或者咱们能够直接使用Python内置的线程安全的存储结构,Queue。
Queue的使用以下:
# Python2.x from Queue import Queue # Python3.x import queue # Python2.x q = Queue() # Python3.x q = queue.Queue() # 存储一个元组到Queue中 q.put((1, 'a')) # q.get()每次获取一个数据,使用下面这种方式能够直接拆分元组 int_data, str_data = q.get()
注意:Queue每次获取数据之后都会把获取的数据删除从内部,因此不用担忧获取到重复的数据。使用q.queue属性,能够获得里面全部的数据,返回的是一个deque对象。
有时咱们但愿让某一个线程进入等待状态来进行一些其余的处理,当咱们某些事情处理完成之后,再唤醒线程,让它从刚才中止的地方继续运行。使用标准库下面的Threading.Event就能够实现。
休眠事件:wait()
唤醒事件:set()
清除事件:clear()
from threading import Event, Thread # 接收一个Event对象 def test_event(e): print('run...') # 让这个线程进入睡眠状态 e.wait() # 当线程被唤醒之后,会输出下面的语句 print('end...') e = Event() t = Thread(target=test_event, args=(e,)) # 这里会看到输出了 run... t.start() print('Set Event...') # 唤醒线程会看到 end... e.set()
上面程序最终运行结果为:
run...
Set Event...
end...
注意:当咱们程序在一块儿运行周期内,重复调用e.wait(),第二次调用就没法让线程进入休眠状态了,须要调用e.clear()清除之后,才能再次进入休眠状态。
from threading import Event, Thread def test_event(e): print('run...') e.wait() # 为了重复使用,须要加上e.clear() # e.clear() print('end...') e = Event() t = Thread(target=test_event, args=(e,)) t.start() # 第一次成功休眠 print('Set Event1...') e.set() t = Thread(target=test_event, args=(e,)) t.start() # 第二次休眠失败 print('Set Event2...') e.set()
不去掉e.clear()的注释,根据线程的切换顺序,可能获得各类输出结果,能够本身屡次尝试看看有什么不一样的结果。
去掉e.clear()的注释之后,输出结果以下:
run...
Set Event1...
end...
run...
Set Event2...
end...
在多线程环境中,咱们有多个继承了Thread的类,他们之间相互调用。假设咱们此时有一个MyThread的类,它是为其余线程服务的。如今,其余线程的全部操做已经所有完成了,而咱们的MyThread的run方法里面有一个死循环,咱们怎么在其余线程都完成工做,中止了之后,中止咱们的MyThread类中的死循环?
from threading import Thread class MyThread(Thread): def run(self): while True: # 控制其余各个线程的代码 pass def __init__(self): Thread.__init__(self) # 设置守护线程 self.setDaemon(True)
在__init__
中,self实际上就是Thread类的对象,因此setDaemon其实是Thread类的一个方法,当设置为True就能够把当前类变成一个守护线程,等到其余线程都中止之后,它会自动中止。
有些场景下,咱们但愿每一个线程,都有本身独立的数据,他们使用同一个变量,可是在每一个线程内的数据都是独立的互不干扰的。
咱们可使用threading.local()来实现:
import threading L = threading.local() L.num = 1 # 此时操做的是咱们当前主线程的threading.local()对象,输出结果为1 print(L.num) def f(): print(L.num) # 建立一个子线程,去调用f(),看可否访问主线程中定义的L.num t = threading.Thread(target=f) t.start() # 结果提示咱们: # AttributeError: '_thread._local' object has no attribute 'num'
对上面的稍做修改:
import threading L = threading.local() L.num = 1 # 此时操做的是咱们当前主线程的threading.local()对象,输出结果为1 print(L.num) def f(): L.num = 5 # 这里能够成功的输出5 print(L.num) # 建立一个子线程,去调用f(),看可否访问主线程中定义的L.num t = threading.Thread(target=f) t.start() # 主线程中的L.num依然是1,没有发生任何改变 print(L.num)
程序运行结果为:
1
5
1
因而可知,threading.local()建立的对象中的属性,是对于每一个线程独立存在的,它们相互之间没法干扰,咱们称它为线程本地数据。
https://www.imooc.com/article/16198
python threading.current_thread().name和.getName()有什么区别 今天学到python多线程这块,想显示当前线程是主线程仍是子线程.网上一搜,有个方法叫 1 threading.current().name 可是发现,一样的threading.current_thread()后面不只仅有.name属性,并且还有.getName()方法.可是 这2个写法最后得出的结果倒是同样的. 那么,2者区别在哪里呢? 1 import threading 2 import time 3 4 def run(arg): 5 print("running sub thread...{}".format(threading.current_thread())) 6 time.sleep(3) 7 8 if __name__ == "__main__": 9 t1 = threading.Thread(target=run,args=("t1",)) 10 t1.start() 11 print("mian Thread...{}".format(threading.current_thread().getName())) 12 print("mian Thread...{}".format(threading.current_thread().name)) 13 14 t2 = threading.Thread() name 是当前线程的属性, getName 是当前线程的方法。 尽管 threading.current_thread().name 和 threading.current_thread().getName() 的结果同样,可是彻底不是同一种东西呀, 例如经过 threading.current_thread().name = 'thread_python' 来改变它。
最终演示代码: 1 import threading 2 import time 3 4 def run(arg): 5 print("running sub thread...{}".format(threading.current_thread())) 6 threading.current_thread().name="xurui_python" 7 print("sub1 Thread...{}".format(threading.current_thread().getName())) 8 print("sub2 Thread...{}".format(threading.current_thread().name)) 9 time.sleep(3) 10 11 if __name__ == "__main__": 12 t1 = threading.Thread(target=run,args=("t1",)) 13 t1.start() 14 print("mian1 Thread...{}".format(threading.current_thread().getName())) 15 print("mian2 Thread...{}".format(threading.current_thread().name)) 代码结果: 1 running sub thread...<Thread(Thread-1, started 23296)> 2 mian1 Thread...MainThread 3 mian2 Thread...MainThread 4 sub1 Thread...xurui_python 5 sub2 Thread...xurui_python
test