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、死锁

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


如何避免死锁:


程序设计时尽量避免

添加超时时间等


相关文章
|
6天前
|
安全 Python
告别低效编程!Python线程与进程并发技术详解,让你的代码飞起来!
【7月更文挑战第9天】Python并发编程提升效率:**理解并发与并行,线程借助`threading`模块处理IO密集型任务,受限于GIL;进程用`multiprocessing`实现并行,绕过GIL限制。示例展示线程和进程创建及同步。选择合适模型,注意线程安全,利用多核,优化性能,实现高效并发编程。
21 3
|
1天前
|
消息中间件 安全 数据处理
Python中的并发编程:理解多线程与多进程的区别与应用
在Python编程中,理解并发编程是提高程序性能和响应速度的关键。本文将深入探讨多线程和多进程的区别、适用场景及实际应用,帮助开发者更好地利用Python进行并发编程。
|
2天前
|
缓存 并行计算 监控
了解 Python 线程
【7月更文挑战第8天】在Python多线程编程中,`threading`模块允许我们获取当前线程名字,通过`current_thread().name`获取。线程名字有助于调试、日志和资源管理。示例代码展示了如何创建线程并打印其名字。在实际应用中,线程命名应清晰、唯一且避免特殊字符,以提高代码可读性和维护性。多线程编程需注意线程安全、死锁、性能优化等问题。通过合理设计和测试,可以利用多线程提高程序并发性和效率。
6 1
|
5天前
|
数据库 数据安全/隐私保护 C++
Python并发编程实战:线程(threading)VS进程(multiprocessing),谁才是并发之王?
【7月更文挑战第10天】Python并发对比:线程轻量级,适合I/O密集型任务,但受GIL限制;进程绕过GIL,擅CPU密集型,但通信成本高。选择取决于应用场景,线程利于数据共享,进程利于多核利用。并发无“王者”,灵活运用方为上策。
|
6天前
|
安全 API 调度
深度剖析:Python并发编程中的线程与进程,那些你不可不知的使用技巧与限制!
【7月更文挑战第9天】Python并发:线程适合IO密集型任务,利用GIL下的多线程同步,如示例中使用锁。进程适用于CPU密集型,通过multiprocessing模块实现多进程,利用进程间通信如队列。线程受限于GIL,进程间通信成本高。选择取决于任务需求和性能目标。
12 2
|
2天前
|
网络协议 安全 Python
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
我们将使用Python的内置库`http.server`来创建一个简单的Web服务器。虽然这个示例相对简单,但我们可以围绕它展开许多讨论,包括HTTP协议、网络编程、异常处理、多线程等。
5 0
|
5天前
|
Python Windows
从菜鸟到大神:一篇文章带你彻底搞懂Python并发编程——线程篇与进程篇的深度较量!
【7月更文挑战第10天】Python并发编程对比线程与进程。线程适合IO密集型任务,利用`threading`模块,但GIL限制CPU并行。进程适用于CPU密集型任务,通过`multiprocessing`实现,独立内存空间,启动成本高。例子展示了如何创建和管理线程与进程以提高效率。选择取决于任务类型和资源需求。
14 0
|
5天前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
18 1
|
5天前
|
设计模式 存储 安全
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:设计一个线程安全的单例类并解释其内存占用情况?使用Java多线程工具类实现一个高效的线程池,并解释其背后的原理。结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
14 1
|
3天前
|
缓存 Linux 编译器
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
【Linux】多线程——线程概念|进程VS线程|线程控制(下)
11 0