Python多线程编程之线程的两种创建方式

本文介绍了在Python中使用threading模块创建多线程的两种方法:直接通过Thread类构造器和继承Thread类。重点讲解了线程的概念、多线程的优势和缺点,以及如何利用Thread类的方法实现多线程任务的并发执行。
摘要由CSDN通过智能技术生成

本次分享主要示例创建多线程的两种方法:

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%免费】
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值