本次分享主要示例创建多线程的两种方法:
1、使用threading模块的Thread类的构造器创建线程(创建Thread类的实例,传递一个函数,简单明了)
2、继承threading模块的Thread类创建线程类(重写run()方法,面向对象的接口)
一般情况下,我们用到的程序完成特定的功能都是单线程运行的,一条线程指的是进程中一个单一顺序的控制流——程序依次执行每行代码,如果程序在某个位置遇到阻塞(出错),则程序在该处停止退出。我们使用DE工具运行调试出bug的程序,很容易定位到运行出 错位置。
但实际对于GUI程序来说,单线程程序往往功能有限,满足不了需求。要解决这个问题就要涉及多线程的知识。
线程是一种对于非顺序依赖的多个任务进行解耦的技术。多线程可以提高应用的响应效率,当接收用户输入的同时,保持其他任务在后台运行。
多线程应用场景:一个浏览器必须同时下载多张图片,一个web服务器必须能同时响应多个用户请求,GUI应用主线程更新界面、子线程实时处理数据等。
一、进程和线程
1、进程是系统中资源分配和资源调度的基本单位,例如QQ、微信、word、浏览器等,每个独立执行的应用程序在系统中都算是一个进程。
2、线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
当进程被初始化后,主线程就被创建了。每个进程中都可以同时包含多个线程,例如qq软件包含很多功能,比如收发信息、下载文件、播放音乐等,这些线程(功能执行程序)可以同时独立运行且互不干扰,这就是使用线程的并发机制——一个进程中可以并发多个线程,每条线程并行执行不同的任务。
注意:
- 并发(Concurrency)指同一时刻只有一条指令执行,但多个线程在系统中快速轮流切换执行有限的CPU时间,我们察觉不到任何中断,这是因为相对于人的感觉,CPU转换执行太快,因此,对我们来说,每个进程好像在同时执行一样。
- 并行(Parallel)指同一时刻有多条进程在多个处理器上同时执行。
二、多线程的优势和缺点
- 多线程优势
主要优势在于充分利用用户事件间很小的CPU时间,在系统后台轮流处理,使得同一个进程可以同时并发处理多个任务。提高用户的响应速度,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。
由于同一进程的所有线程是共享同一内存资源,因此,编程更加方便,线程之间很容易通信,不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。
- 多线程缺点
大量的线程会占用大量的处理器时间,造成大多数线程进度明显滞后。过多线程调度会带来很大的性能开销。
创建进程和线程的数目会受到内存的限制;确保线程不会妨碍同一进程中其他线程;销毁线程需要了解可能发生的问题并进行处理。
三、Thread类方法
Python中主要用threading模块来支持多线程,Thread类主要方法及说明见下表:
Thread类方法 | |
__init__(self,group=None,target=None,name=None,args=(),kwargs=None,*,daemon=None) | 实例化一个线程对象,需要一个可调用的target对象,以及参数args(指定一个元组)或者kwargs(指定一个字典,以关键字参数形式为target指定函数的传入参数)。还可以传递name参数。daemon的值将会设定thread.daemon的属性,指定所构建的线程是否为后台线程。 |
start() | 开始执行该线程 |
run() | 定义线程的方法。(通常应该在子类中重写) |
join(timeout=None) | 直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞即让一个线程等待另外一个线程完成的方法。timeout参数,该指定等待被join的线程时间最长为timeout秒。 |
Thread类属性包括name(线程名)、ident(线程的标识符)、daemon(布尔值,表示线程是否为后台线程)
四、两种创建线程方法
- 使用threading模块的Thread类的构造器创建线程
1 默认启动主线程
程序默认执行只在一个线程,这个线程称为主线程(默认情况下,名称为MainThread)。创建一个"干活"的主线程:
#threading的current_thread()方法返回当前线程
t = threading.current_thread()
#返回指定线程的名字,其他常用方法,getName()获取线程id,i判断线程是否存活等。
print(t) #<_MainThread(MainThread, started 14440)>
print(t.getName()) # MainThread
print(t.ident) # 14440
print(t.isAlive()) # True
t.setName("THREAD") #为线程设置名称
print(t.getName()) #THREAD
注:threading.active_count() #获取已激活的线程数
2 创建线程
创建Thread类的实例,传递一个函数
创建一个线程
my_thread = threading.Thread()
创建一个名称为my_thread的线程
my_thread = threading.Thread(name=‘my_thread’,target=thread_job)
注:接收参数 target 代表这个线程要完成的任务,需要做些什么,需自行定义可调用函数。
#自定义任务函数
def action(i):
print(threading.current_thread().getName()+'开始运行:'+str(i))
my_thread = threading.Thread(target=action,args=(1,)) #创建线程
my_thread.start() #调用线程对象my_thread的start()方法启动线程。
3、创建多线程
创建一个多线程,启动多个线程名称依次为Thread-1、Thread-2、…Thread-n等:
import threading
from datetime import datetime
import time
def action(max):
for i in range(max):
time.sleep(0.1)
print('当前线程%s,运行结束时间为:%s' % (threading.current_thread().getName(), datetime.today()))
def main():
threads = [threading.Thread(target=action,args=(5,)) for i in range(3)]
[threads[i].start() for i in range(len(threads))]
if __name__=='__main__':
main()
运行以上代码,启动3个线程,运行结果如下,默认Thread-1、Thread-2、Thread-3三个线程。注意观察,这三个线程执行没有先后顺序,以并发的方式执行,线程间轮换执行一段时间,从而给我们带来多个线程同时执行的错觉。
当前线程Thread-2,运行结束时间为:2020-12-19 16:57:21.503568
当前线程Thread-1,运行结束时间为:2020-12-19 16:57:21.503568
当前线程Thread-3,运行结束时间为:2020-12-19 16:57:21.503568
当前线程Thread-3,运行结束时间为:2020-12-19 16:57:21.604272
当前线程Thread-1,运行结束时间为:2020-12-19 16:57:21.604272
当前线程Thread-2,运行结束时间为:2020-12-19 16:57:21.604272
当前线程Thread-2,运行结束时间为:2020-12-19 16:57:21.705032
当前线程Thread-1,运行结束时间为:2020-12-19 16:57:21.705032
当前线程Thread-3,运行结束时间为:2020-12-19 16:57:21.705032
当前线程Thread-1,运行结束时间为:2020-12-19 16:57:21.805765
当前线程Thread-2,运行结束时间为:2020-12-19 16:57:21.805765
当前线程Thread-3,运行结束时间为:2020-12-19 16:57:21.805765
当前线程Thread-3,运行结束时间为:2020-12-19 16:57:21.906463
当前线程Thread-2,运行结束时间为:2020-12-19 16:57:21.906463
当前线程Thread-1,运行结束时间为:2020-12-19 16:57:21.906463
被操作系统轮询分配CPU执行时间,时间耗尽中断此线程执行,然后切换给其他线程,以此轮换运行…
- 继承threading模块的Thread类创建线程类
MyThread子类的构造函数必须先调用其父类(Thread)的构造函数,并重写run()方法,增加自定义调试信息的输出以及结果的输出方法。
单线程模式简单一次调用每个函数,并将结果打印出来;而多线程将3个计算任务运行在3个并行的线程中,不会立即显示结果,等到所有线程都join后,再调用getRes()方法显示每个函数的返回值。
以下代码展示了 多线程threading 模块如何在后台运行任务,且不影响主程序的继续运行。
#继承Thread类创建线程类
class MyThread(threading.Thread):
def __init__(self,func,args,name=''):
threading.Thread.__init__(self)
self.func=func
self.name = name
self.args=args
#重写run()方法,此处自定义设置
def run(self):
start = time.time()
print('当前线程%s,开始运行时间为:%s' % (self.name, datetime.today()))
self.res = self.func(*self.args)
stop = time.time()
print('当前线程%s,运行结束时间为:%s' % (self.name, datetime.today()))
ret=stop - start
print('当前线程%s,执行消耗时间为:%s' % (self.name,ret))
#返回结果
def getRes(self):
return self.res
#完成以下三个任务
# 斐波那契
def fib(x):
time.sleep(0.005)
if x < 2:
return 1
return fib(x - 1) + fib(x - 2)
# 阶乘
def fac(x):
time.sleep(0.1)
if x < 2:
return 1
return x * fac(x - 1)
# 累加
def sum(x):
time.sleep(0.1)
if x < 2:
return 1
return x + sum(x - 1)
#功能函数名称列表
funcs = [fib, fac, sum]
#传入计算参数
n = 10
#主函数执行体
def main():
nfuncs = range(len(funcs))
# 单线程
print('单线程模式')
for i in nfuncs:
print('当前线程%s,开始运行时间为:%s' % (funcs[i].__name__, datetime.today()))
print(funcs[i](n))
print('当前线程%s,运行结束时间为:%s' % (funcs[i].__name__, datetime.today()))
# 多线程
print('多线程模式')
threads = [MyThread(funcs[i], (n,), funcs[i].__name__) for i in nfuncs]
[threads[i].start() for i in nfuncs] #开始创建所有的线程
for i in nfuncs:
threads[i].join()
print(threads[i].getRes()) #等待所有的线程执行完毕
print('任务结束')
if __name__ == '__main__':
main()
对比单线程和多线程的结果:
单线程模式
当前线程fib,开始运行时间为:2020-12-19 19:15:35.602806
89
当前线程fib,运行结束时间为:2020-12-19 19:15:36.626262
当前线程fac,开始运行时间为:2020-12-19 19:15:36.626262
3628800
当前线程fac,运行结束时间为:2020-12-19 19:15:37.633569
当前线程sum,开始运行时间为:2020-12-19 19:15:37.633569
55
当前线程sum,运行结束时间为:2020-12-19 19:15:38.640929
多线程模式
当前线程fib,开始运行时间为:2020-12-19 19:15:38.640929
当前线程fac,开始运行时间为:2020-12-19 19:15:38.641894
当前线程sum,开始运行时间为:2020-12-19 19:15:38.641894
当前线程sum,运行结束时间为:2020-12-19 19:15:39.649204
当前线程sum,执行消耗时间为:1.007310152053833
当前线程fac,运行结束时间为:2020-12-19 19:15:39.649204
当前线程fac,执行消耗时间为:1.007310152053833
当前线程fib,运行结束时间为:2020-12-19 19:15:39.699070
当前线程fib,执行消耗时间为:1.0581409931182861
89
3628800
55
任务结束
总的来说,把任务函数的运行分配到多个线程上,每个线程处理一个请求,本线程阻塞不影响新请求进入,这能一定程度上加快用户的操作响应。
多线程应用设计需要协调多个线程之间需要共享数据资源,通常程序无法准确控制线程的轮换执行,容易突然出现“错误情况”,因为系统中线程调度具有一定的随机性。多线程threading 模块提供了多个同步操作,包括线程锁、事件、条件变量和信号量。
Python中可通过线程通信来保证线程的多任务协调运行,主要包括:使用Condition控制线程通信、使用队列Queue控制线程通信、使用Event控制线程通信。
最后:
我准备了一些非常系统的Python资料,除了为你提供一条清晰、无痛的学习路径,我还甄选了最实用的学习资源以及庞大的主流python案例库。短时间的学习,你就能够很好地掌握python这个技能,获取你想得到的数据,需要的朋友可以扫描文末二维码即可获取。
01 专为0基础设置,小白也能轻松学会
我们把Python的所有知识点,都穿插在了漫画里面。
在Python小课中,你可以通过漫画的方式学到知识点,难懂的专业知识瞬间变得有趣易懂。
你就像漫画的主人公一样,穿越在剧情中,通关过坎,不知不觉完成知识的学习。
02 无需自己下载安装包,提供详细安装教程
03 规划详细学习路线,提供学习视频
04 提供实战资料,更好巩固知识
05 提供面试资料以及副业资料,便于更好就业
这份完整版的Python全套学习资料已经上传CSDN,朋友们如果需要也可以扫描下方csdn官方二维码或者点击主页和文章下方的微信卡片获取领取方式,【保证100%免费】