Python 多线程之threading介绍

简介: Python 多线程之threading介绍

一、为什么要有多任务?

平时唱歌跳舞都是一起进行的,如果先唱歌后跳舞,或者先跳舞后唱歌,感觉很不协调,别扭—所以需要一个多任务

案例:

8020d34ed72bd7c9daf284ab4eaf5b8.png

from time import sleep
def sing():
    for i in range(3):
        print("正在唱歌....%d"%i)
        sleep(1)
def dance():
    for i in range(3):
        print("正在跳舞....%d"%i)
        sleep(1)
if __name__ == '__main__':
    sing()
    dance()

二、多任务介绍

概念:多任务就是可以一边听歌,一边上网冲浪,或者在干点其它什么的,可以同时有2个以上的任务在执行

注意:

并发:某个时间段内,多个任务交替执行。当有多个线程在操作时,把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态(暂停)。

并行:同一时间处理多任务的能力,多有多个线程在操作时,CPU同时处理这些线程请求的能力。


三、threading介绍

Python的thread模块是比较底层的,Python的threading模块是对thread做了包装,使用更加方便。


threading常用方法:


方法名 解释

threading.active_count() 返回当前处于active状态的Thread对象

threading.current_thread() 返回当前Thread对象

threading.get_ident() 返回当前线程的线程标识符,线程标识符是一个非负整数,并无特殊含义,知识用来标识线程,该证书可能会被循环利用。Python3.3版本后支持该方法

threading.enumerate() 返回当前处于active解释器的线程对象。Python3.4版本以后支持该方法

threading.main_thread() 返回主线程对象,即启动Python解释器的线程对象。Python3.4版本以后支持该方法

threading.stack_size() 返回创建线程时使用的栈的大小,如果指定size参数,则用来指定后续创建的线程使用的栈大小,size必须是0(标识使用系统默认值)或大于32K正整数

1、Thread类使用说明

threading模块提供了Thread、Lock、RLock、Conditon、Event、Timer和Semaphore等类来支持多线程,Thread是其中最重要也是最基本的一个类,通过该类创建线程并控制线程的运行。


使用Thread创建线程的方法:


  • 为构造函数传递一个可调用对象
  • 集成Thread类并在子类中重写__init__()和run()方法
  • 语法格式:threading.Thread(group=None,target=None,name=None,args=(),kwargs={},*,daemon=None)
  • 参数说明:
  • group:通常默认即可,作为日后扩展ThreadGroup类实现而保留。
  • target:用于run()方法调用的可调用对象,默认为None
  • name:线程名,默认是Thread-N格式构成唯一名称,N是十进制数(正整数)
  • args:用于调用目标函数的关键字参数字典,默认为{}
  • daemon:设置线程是否为守护模式,默认None

threading.Thread的方法和属性:

|方法名 | 解释 |

|–|–|

| start() | 启动线程 |

|run()|线程代码,用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为|

|init(self,group=None,target=None,name=None,args=(),kwargs=None,daemon=None)|构造函数|

|is_alive()|判断线程是否存活|

|getName()|返回线程名|

|setName()|设置线程名|

|isDaemon()|判断线程是否为守护线程|

|setDaemon()|设置线程是否为守护线程|

|name|用来读取或设置线程的名字|

|ident|线程标识,用非0数字或None(线程未被启动)|

|daemon|线程是否为守护线程,默认false|

|join(timeout=None)|当timeout=None时,会等待至线程结束;当非None时,会等待timeout时间结束,单位秒|

2、实例化threading.Thread(重点)

1)单线程执行 --时间间隔

1ebb544fa523c706d27fbd9288848cb.png

from time import sleep
import datetime
def sing():
    print("正在唱歌....")
    sleep(1)
if __name__ == '__main__':
    for i in range(3):
        sing()
        print(datetime.datetime.now())

2)使用threading模块



import threading
from time import sleep
import datetime
def sing():
    print("正在唱歌....")
    sleep(1)
if __name__ == '__main__':
    for i in range(3):
        t = threading.Thread(target=sing)
        t.start()
        print(datetime.datetime.now())

看执行速度能对比出来threading多线程速度飞快

调用start()才会创建真正的多线程,开始执行代码

3)主线程等待所有子线程结束后才结束

7933292d04e1369fb9c4006dc989522.png


import threading
from time import sleep, ctime
def sing():
    for i in range(3):
        print("正在唱歌....%d" % i)
        sleep(1)
def dance():
    for i in range(3):
        print("正在跳舞....%d" % i)
        sleep(1)
if __name__ == '__main__':
    print("开始:", ctime())
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    print("结束: ", ctime())

4)查看线程数

c468de5ec8170c12e81597176239a73.png


import threading
from time import sleep, ctime
def sing():
    for i in range(3):
        print("正在唱歌....%d" % i)
        sleep(1)
def dance():
    for i in range(3):
        print("正在跳舞....%d" % i)
        sleep(1)
if __name__ == '__main__':
    print("开始:", ctime())
    t1 = threading.Thread(target=sing)
    t2 = threading.Thread(target=dance)
    t1.start()
    t2.start()
    while True:
        num = len(threading.enumerate())
        print("线程数: %d" % num)
        if (num) < 1:
            break
        sleep(1)
    print("结束: ", ctime())

四、继承threading.Thread

1、线程执行封装代码

使用threading线程模块封装时,定义一个新的子类class,继承threading.Thread,重写run方法

15a64c1c8f3fa628aa469f04eebaa04.png

import threading
from time import sleep
class MyClass(threading.Thread):
    def run(self):
        for i in range(3):
            sleep(1)
            msg = self.name + "--->" + str(i)
            print(msg)
if __name__ == '__main__':
    t = MyClass()
    t.start()

Python的threading.Thread类有一个run方法,定义线程功能函数,可以覆盖该方法,创建自己的线程实例后,调用start启动,交给虚拟机进行调度,有执行机会就会调用run方法执行线程。

2、线程执行顺序


52ed0aa3e5e356e56d46665a65bff03.png

import threading
from time import sleep
class MyClass(threading.Thread):
    def run(self):
        for i in range(3):
            sleep(1)
            msg = self.name + "--->" + str(i)
            print(msg+'\n')
def test():
    for i in range(5):
        t = MyClass()
        t.start()
if __name__ == '__main__':
    test()

多线程执行顺序是不确定的,sleep时,线程将被阻塞(Blocked),到sleep结束后,线程进入就绪(Runnable)状态,等待调度。线程调度选择一个线程执行。以上代码能保证运行每个线程都运行完整的run函数,但是线程启动顺序、run函数每次循环执行的顺序不能确定。


总结:


每个线程默认有一个名字

当线程run方法结束时,该线程完成。

无法控制线程调度顺序,可以通过其它办法影响线程调度方式。

五、多线程共享全局变量(重点)


0a0c2c6b69ec4105b5a84df495f6ff1.png

from threading import Thread
from time import sleep
gl_num = 10
def sing():
    global gl_num
    for i in range(3):
        gl_num += 1
    print("sing------>%d"%gl_num)
def dance():
    global gl_num
    print("sing------>%d" % gl_num)
t1 = Thread(target=sing)
t1.start()
sleep(1)
t2 = Thread(target=dance)
t2.start()

列表当做参数传递:

076edb736ebbca28010e0fef94ad2c5.png

from threading import Thread
from time import sleep
gl_num = [10, 20, 30]
def sing(num):
    num.append(33)
    print("sing------>", num)
def dance(num):
    sleep(1)
    print("sing------>", num)
t1 = Thread(target=sing, args=(gl_num,))
t1.start()
t2 = Thread(target=dance, args=(gl_num,))
t2.start()

同进程内的所有线程共享全局变量,方便多个线程共享数据

缺点,线程一单被修改造成多线程之间全局变量混乱现象(使用时保证全局变量不能被修改,线程非安全的)

六、线程同步和锁(重点)

1、线程同步

概念:协同步调,按预定的先后顺序执行,多线程修改全局变量,可能会出现意外结果,为了保证数据的正确性,需要对多个线程进行同步。

方法:使用Thread对象的Lock和Rlock实现简单线程同步,acquire和release方法对只允许一个线程操作的数据,可以将其放到acquire和release之间。

2、互斥锁

某个线程需要修改共享数据时,先将其锁定,此时资源状态为锁定,其它线程不允许修改;直到该线程释放资源,将资源状态调整非锁定,其它线程才能再次锁定该共享数据。互斥锁保证每次只有一个线程进行数据修改,能够保证多线程数据正确性。


threading模块中定义Lock类,方便的处理锁:

import threading
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 解锁
mutex.release()

调用acquire之前没有锁,不会堵塞;已锁,会堵塞,直到这个锁解锁


案例:互斥锁两个线程对统一变量累加10万次



import threading
from time import sleep
gl = 0
mutex = threading.Lock()
def sing(num):
    global gl
    for i in range(num):
        mutex.acquire()
        gl += 1
        mutex.release()
    print("唱歌-----> %d" % gl)
def dance(num):
    global gl
    for i in range(num):
        mutex.acquire()
        gl += 1
        mutex.release()
    print("跳舞------>%d" % gl)
t1 = threading.Thread(target=sing, args=(100000,))
t1.start()
t2 = threading.Thread(target=dance, args=(100000,))
t2.start()
while len(threading.enumerate()) != 1:
    sleep(1)
print("最终结果: %d" % gl

锁的拆解过程:


当一个线程调用acquire()方法时锁定,状态是locked

-只允许有一个线程获得锁,另一个线程想使用,该线程变成blocked状态,阻塞,直至有锁的线程调用release()方法释放锁,此时变成unlocked状态。

-线程调度从阻塞线程中选择一个来调用锁,并使此线程进入运行running状态。

总结:


锁好处:确保了代码可以由某一线程从开始到结束独立运行

锁坏处:阻止了多线程并发执行,单线程执行效率低下;使用多个锁可能会造成死锁


3、死锁

解释:线程间共享多个资源时,两个线程并各站一部分资源,同时等待对方释放资源,会造成死锁。


如何避免死锁:


程序设计时尽量避免

添加超时时间等


相关文章
|
人工智能 安全 调度
Python并发编程之线程同步详解
并发编程在Python中至关重要,线程同步确保多线程程序正确运行。本文详解线程同步机制,包括互斥锁、信号量、事件、条件变量和队列,探讨全局解释器锁(GIL)的影响及解决线程同步问题的最佳实践,如避免全局变量、使用线程安全数据结构、精细化锁的使用等。通过示例代码帮助开发者理解并提升多线程程序的性能与可靠性。
393 0
|
11月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
919 0
|
数据采集 消息中间件 并行计算
Python多线程与多进程性能对比:从原理到实战的深度解析
在Python编程中,多线程与多进程是提升并发性能的关键手段。本文通过实验数据、代码示例和通俗比喻,深入解析两者在不同任务类型下的性能表现,帮助开发者科学选择并发策略,优化程序效率。
912 1
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
数据采集 存储 Java
多线程Python爬虫:加速大规模学术文献采集
多线程Python爬虫:加速大规模学术文献采集
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
353 3
|
数据采集 Java 数据处理
Python实用技巧:轻松驾驭多线程与多进程,加速任务执行
在Python编程中,多线程和多进程是提升程序效率的关键工具。多线程适用于I/O密集型任务,如文件读写、网络请求;多进程则适合CPU密集型任务,如科学计算、图像处理。本文详细介绍这两种并发编程方式的基本用法及应用场景,并通过实例代码展示如何使用threading、multiprocessing模块及线程池、进程池来优化程序性能。结合实际案例,帮助读者掌握并发编程技巧,提高程序执行速度和资源利用率。
889 0
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
468 1
如何确保Python Queue的线程和进程安全性:使用锁的技巧
Python中的多线程与多进程
本文将探讨Python中多线程和多进程的基本概念、使用场景以及实现方式。通过对比分析,我们将了解何时使用多线程或多进程更为合适,并提供一些实用的代码示例来帮助读者更好地理解这两种并发编程技术。

推荐镜像

更多