今天开始打算开一个新系列,就是python的多线程和多进程实现,这部分可能有些新手仍是比较模糊的,都知道python中的多线程是假的,可是又不知道怎么回事,首先咱们看一个例子来看看python多线程的实现。python
import threading
import time
def say(name):
print('你好%s at %s' %(name,time.ctime()))
time.sleep(2)
print("结束%s at %s" %(name,time.ctime()))
def listen(name):
print('你好%s at %s' % (name,time.ctime()))
time.sleep(4)
print("结束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
t1 = threading.Thread(target=say,args=('tony',)) #Thread是一个类,实例化产生t1对象,这里就是建立了一个线程对象t1
t1.start() #线程执行
t2 = threading.Thread(target=listen, args=('simon',)) #这里就是建立了一个线程对象t2
t2.start()
print("程序结束=====================")
结果:
你好tony at Thu Apr 25 16:46:22 2019
你好simon at Thu Apr 25 16:46:22 2019
程序结束=====================
结束tony at Thu Apr 25 16:46:24 2019
结束simon at Thu Apr 25 16:46:26 2019
Process finished with exit code 0
复制代码
python的多线程是经过threading模块的Thread类来实现的。 建立线程对象 t1 = threading.Thread(target=say,args=('tony',)) #Thread是一个类,实例化产生t1对象,这里就是建立了一个线程对象t1 启动线程 t1.start() #线程执行bash
下面咱们分析下上面代码的结果:
你好tony at Thu Apr 25 16:46:22 2019 --t1线程执行
你好simon at Thu Apr 25 16:46:22 2019 --t2线程执行
程序结束===================== --主线程执行
结束tony at Thu Apr 25 16:46:24 2019 --sleep以后,t1线程执行
结束simon at Thu Apr 25 16:46:26 2019 --sleep以后,t2线程执行
Process finished with exit code 0 --主线程结束
复制代码
咱们能够看到主线程的print并非等t1,t2线程都执行完毕以后才打印的,这是由于主线程和t1,t2 线程是同时跑的。可是主进程要等非守护子线程结束以后,主线程才会退出。多线程
上面其实就是python多线程的最简单用法,可是可能有人会和我有同样的需求,通常开发中,咱们须要主线程的print打印是在最后面的,代表全部流程都结束了,也就是主线程结束了。这里就引入了一个join的概念。并发
import threading
import time
def say(name):
print('你好%s at %s' %(name,time.ctime()))
time.sleep(2)
print("结束%s at %s" %(name,time.ctime()))
def listen(name):
print('你好%s at %s' % (name,time.ctime()))
time.sleep(4)
print("结束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
t1 = threading.Thread(target=say,args=('tony',)) #Thread是一个类,实例化产生t1对象,这里就是建立了一个线程对象t1
t1.start() #线程执行
t2 = threading.Thread(target=listen, args=('simon',)) #这里就是建立了一个线程对象t2
t2.start()
t1.join() #join等t1子线程结束,主线程打印而且结束
t2.join() #join等t2子线程结束,主线程打印而且结束
print("程序结束=====================")
结果:
你好tony at Thu Apr 25 16:57:32 2019
你好simon at Thu Apr 25 16:57:32 2019
结束tony at Thu Apr 25 16:57:34 2019
结束simon at Thu Apr 25 16:57:36 2019
程序结束=====================
复制代码
上面代码中加入join方法后实现了,咱们上面所想要的结果,主线程print最后执行,而且主线程退出,注意主线程执行了打印操做和主线程结束不是一个概念,若是子线程不加join,则主线程也会执行打印,可是主线程不会结束,仍是须要待非守护子线程结束以后,主线程才结束。性能
上面的状况,主进程都须要等待非守护子线程结束以后,主线程才结束。那咱们是否是注意到一点,我说的是“非守护子线程”,那什么是非守护子线程?默认的子线程都是主线程的非守护子线程,可是有时候咱们有需求,当主进程结束,无论子线程有没有结束,子线程都要跟随主线程一块儿退出,这时候咱们引入一个“守护线程”的概念。ui
若是某个子线程设置为守护线程,主线程其实就不用管这个子线程了,当全部其余非守护线程结束,主线程就会退出,而守护线程将和主线程一块儿退出,守护主线程,这就是守护线程的意思spa
看看具体代码,咱们这里分2种状况来讨论守护线程,加深你们的理解, 还有一点,这个方法必定要设置在start方法前面线程
1.设置t1线程为守护线程,看看执行结果code
import threading
import time
def say(name):
print('你好%s at %s' %(name,time.ctime()))
time.sleep(2)
print("结束%s at %s" %(name,time.ctime()))
def listen(name):
print('你好%s at %s' % (name,time.ctime()))
time.sleep(4)
print("结束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
t1 = threading.Thread(target=say,args=('tony',)) #Thread是一个类,实例化产生t1对象,这里就是建立了一个线程对象t1
t1.setDaemon(True)
t1.start() #线程执行
t2 = threading.Thread(target=listen, args=('simon',)) #这里就是建立了一个线程对象t2
t2.start()
print("程序结束=====================")
结果:
你好tony at Thu Apr 25 17:11:41 2019
你好simon at Thu Apr 25 17:11:41 2019
程序结束=====================
结束tony at Thu Apr 25 17:11:43 2019
结束simon at Thu Apr 25 17:11:45 2019
复制代码
注意执行顺序,
这里若是设置t1为Daemon,那么主线程就无论t1的运行状态,只管等待t2结束, t2结束主线程就结束了
由于t2的时间4秒,t1的时间2秒,主线程在等待t2线程结束的过程当中,t1线程本身结束了,因此结果是:
你好tony at Thu Apr 25 14:11:54 2019
你好simon at Thu Apr 25 14:11:54 2019程序结束===============
结束tony at Thu Apr 25 14:11:56 2019 (也会打印,由于主线程在等待t2线程结束的过程当中, t1线程本身结束了)
结束simon at Thu Apr 25 14:11:58 2019
复制代码
2.设置t2为守护线程协程
import threading
import time
def say(name):
print('你好%s at %s' %(name,time.ctime()))
time.sleep(2)
print("结束%s at %s" %(name,time.ctime()))
def listen(name):
print('你好%s at %s' % (name,time.ctime()))
time.sleep(4)
print("结束%s at %s" % (name,time.ctime()))
if __name__ == '__main__':
t1 = threading.Thread(target=say,args=('tony',)) #Thread是一个类,实例化产生t1对象,这里就是建立了一个线程对象t1
t1.start() #线程执行
t2 = threading.Thread(target=listen, args=('simon',)) #这里就是建立了一个线程对象t2
t2.setDaemon(True)
t2.start()
print("程序结束=====================")
结果:
你好tony at Thu Apr 25 17:15:36 2019
你好simon at Thu Apr 25 17:15:36 2019
程序结束=====================
结束tony at Thu Apr 25 17:15:38 2019
复制代码
注意执行顺序:
这里若是设置t2为Daemon,那么主线程就无论t2的运行状态,只管等待t1结束, t1结束主线程就结束了
由于t2的时间4秒,t1的时间2秒, 主线程在等待t1线程结束的过程当中, t2线程本身结束不了,因此结果是:
你好tony at Thu Apr 25 14:14:23 2019
你好simon at Thu Apr 25 14:14:23 2019
程序结束 == == == == == == == == == == =
结束tony at Thu Apr 25 14:14:25 2019
结束simon at Thu Apr 25 14:11:58 2019 不会打印,由于主线程在等待t1线程结束的过程当中, t2线程本身结束不了,t2的时间4秒,t1的时间2秒
复制代码
不知道你们有没有弄清楚上面python多线程的实现方式以及join,守护线程的用法。
主要方法:
join():在子线程完成运行以前,这个子线程的父线程将一直被阻塞。
setDaemon(True):
将线程声明为守护线程,必须在start() 方法调用以前设置, 若是不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。
当咱们在程序运行中,执行一个主线程,若是主线程又建立一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。可是有时候咱们须要的是 只要主线程
完成了,无论子线程是否完成,都要和主线程一块儿退出,这时就能够 用setDaemon方法啦
复制代码
其余方法:
run(): 线程被cpu调度后自动执行线程对象的run方法
start():启动线程活动。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
复制代码
上面的例子中咱们注意到两若是个任务若是顺序执行要6s结束,若是是多线程执行4S结束,性能是有所提高的,可是咱们要知道这里的性能提高其实是因为cpu并发实现性能提高,也就是cpu线程切换(多道技术)带来的,而并非真正的多cpu并行执行。
上面提到了并行和并发,那这二者有什么区别呢? 并发:是指一个系统具备处理多个任务的能力(cpu切换,多道技术) 并行:是指一个系统具备同时处理多个任务的能力(cpu同时处理多个任务) 并行是并发的一种状况,子集
那为何python在多线程中为何不能实现真正的并行操做呢?就是在多cpu中执行不一样的线程(咱们知道JAVA中多个线程能够在不一样的cpu中,实现并行运行)这就要提到python中大名鼎鼎GIL,那什么是GIL?
GIL:全局解释器锁 不管你启多少个线程,你有多少个cpu, Python在执行的时候只会的在同一时刻只容许一个线程(线程之间有竞争)拿到GIL在一个cpu上运行,当线程遇到IO等待或到达者轮询时间的时候,cpu会作切换,把cpu的时间片让给其余线程执行,cpu切换须要消耗时间和资源,因此计算密集型的功能(好比加减乘除)不适合多线程,由于cpu线程切换太多,IO密集型比较适合多线程。
任务: IO密集型(各个线程都会都各类的等待,若是有等待,线程切换是比较适合的),也能够采用能够用多进程+协程 计算密集型(线程在计算时没有等待,这时候去切换,就是无用的切换),python不太适合开发这类功能
咱们前面举得例子里面模拟了sleep操做,其实就是至关于遇到IO,这种场景用多线程是能够增长性能的,可是若是咱们用多线程来计算数据的计算,性能反而会下降。
下面是GIL的一段原生解释:
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.)
复制代码
我的看法,望指教