Python中的多线程
Python中的多任务
- 多任务的实质:时间片轮转
- 并发 和 并行 的区别
- 并发:假的多任务
- 并行:真的多任务
线程
- 一个程序运行起来之后一定有一个执行代码的东西。这个东西我们称之为线程。
- 当创建Thread类的对象是不会创建子线程,而是在启动线程时完成创建。(即在调用start()函数后完成创建)
- 如果创建Thread时执行的函数结束了,意味着该子线程结束…
- 当子线程daemon属性为False时,主线程需要等待所有子线程结束后才能结束。
threading模块
- threading模块是Python支持多线程编程的重要模块,该模块是在底层模块_thread的基础上开发的更高层次的线程编程接口,提供了大量的方法和类来支持多线程任务。
- threading常用方法如下:
方法 | 功能说明 |
---|---|
threading.active_count() | 返回当前处于alive状态的Thread对象数量 |
threading.current_thread() | 返回当前Thread对象 |
threading.get_ident() | 返回当前线程的线程标识符。线程标识符是一个非负整数,并无特殊含义,只是用来标识线程,该整数可能会被循环利用。Python3.3及以后版本支持该方法 |
threading.enumerate() | 返回当前处于alive状态的所有Thread对象列表 |
threading.main_thread() | 返回主线程对象,即启动Python解释器的线程对象。Python3.4版本以后支持该方法 |
threading.stack_size([size]) | 返回创建线程时使用的栈的大小,如制定size参数,则用指定后续创建的线程使用的栈的大小,size必须是0(表示使用系统默认值)或大于32K的正整数 |
Thread类
- Thread类中的方法
方法 | 功能说明 |
---|---|
start() | 自动调用run()方法,启动线程,执行线程代码 |
run() | 线程代码,用来实现线程的功能与业务逻辑,可以在子类中重写该方法来自定义线程的行为 |
join([timeout]) | 阻塞当前线程,等待被调线程结束或超时后再继续执行当前线程的后续代码,参数timeout用来指定最长等待时间,单位为 秒。 |
isAlive() | 测试线程是否处于运行状态,返回值为布尔值 |
多线程的daemon属性
- 主线程和子线程:
- 在脚本运行过程中有一个主线程,若主线程中创建了子线程,当主线程结束时根据子线程daemon属性值的不同可能发生下面的两种情况之一:
- ①当某子线程的daemon属性值为Fasle是,主线程结束时会检测该子线程是否结束,如果该子线程尚未完成,则主线程会等待他完后后再退出;
- ②当某子线程的daemon属性值为True是,主线程运行结束时不对该子线程进行检测而是直接退出,同时所有daemon值为True的子线程将随主线程一起结束,而不论其是否完成。
- 在脚本运行过程中有一个主线程,若主线程中创建了子线程,当主线程结束时根据子线程daemon属性值的不同可能发生下面的两种情况之一:
- daemon属性的值默认为False,如果需要修改,则必须在调用start()方法启动线程之前进行修改daemon属性值。
Thread类对象.daemon = True
- 请注意:
- 以上论述不适用于 IDLE环境中的交互模式或脚本运行模式,因为在该环境中的主线程只有在退出Python IDLE时才终止。
创建线程
- Thread创建线程,完成多任务:
- 导入threading模块
- 创建Thread类的对象
- 启动线程(调用start()方法)
import time # 导入threading模块 import threading def sing(): for i in range(5): print("---唱歌---") time.sleep(1) def dance(): for i in range(5): print("---跳舞---") time.sleep(1) def main(): # 创建Thread类的对象 t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) # 启动线程 t1.start() t2.start() if __name__ == "__main__": main()
- 通过继承Thread类创建线程
- 导入threading模块
- 定义一个类继承Thread类
- 在重写run()方法(方法的重载)
- 创建定义类的对象
- 启动线程(调用start()方法)
import time import threading class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) # name属性中保存的是当前线程的名字 msg = "I'm " + slef.name + ' @ ' + str(i) print(msg) if __name__ == "__main__": t = Mythread t.start()
- 查看程序中正在运行的线程:
- threading模块中提供了enumerate()函数可查看程序中正在运行的线程,其返回值为一个列表。
- 如若查看程序中正在运行的线程个数:
- 调用threading.active_count()方法
- len(enumerate()函数返回值)
- Thread类的 target参数 和 args参数:
- target参数:指定将来这个线程 去哪个函数执行代码(只带函数名不加括号)。
- args参数:指定将来调用函数的时候 传递什么数据过去。
- args参数为一个元组!
import threading def text(num): print(num) a_num = [11, 22] def main(): # 注意:args 参数是一个元组 t1 = threading.Thread(target=text, args=(a_num,)) t1.start() if __name__ == "__main__": main()
- 线程同步技术
- 同步概念:同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
- “同”字从字面上容易理解为一起动作
- 其实则不是,“同”字是指协同、协助、互相配合。
- 如进程、线程同步,可以理解为进程或线程A和B一块配合,A执行到一定程度是要依靠B的某个结果,于是停下来示意B执行,再将结果给A,A在继续操作。
- 线程同步能保证多个线程安全访问竞争资源最简单的同步机制是引入互斥锁。
- 互斥锁
- 当多个线程几乎同时修改某一个共享变量的时候,需要进行同步控制。
- 互斥锁为共享资源引入一种状态:锁定/非锁定
- 某个线程需要修改共享资源时,先将其锁定,此时资源的状态为“锁定”,其他线程不能访问该共享资源,直到该线程释放共享资源,将资源的状态变成“非锁定”,其他线程才能再次锁定该资源。
- 互斥锁保证了每次有且仅有一个线程访问资源进行操作,从而保证了多线程下的数据的正确性。
- 可利用互斥锁解决资源竞争问题:
- 导入threading模块
- 创建互斥锁(即创建Lock类的对象)
- 锁定操作(调用acquire方法)
- 释放解锁(调用release方法)
- 注意:
- 如果这个锁之前是没有上锁的,那么acquire不会堵塞。
- 如果这个锁之前是锁定的,那么acquire会发生堵塞,直至这个锁被解锁为止。
# 导入threading模块 import threading # 创建互斥锁 mutex = threading.Lock() # 锁定操作(调用acquire方法) mutex.acquire() # 释放解锁(调用release方法) mutex.release()
- 使用示例:
import time import threading def add1(): global g_num for i in range(1000000): # 上锁操作 # 如果之前没有被上锁,那么此时上锁成功 # 如果之前已被上锁,那么此时发生堵塞 mutex.acquire() g_num +=1 # 解锁操作 mutex.release() print("---add1---%s", str(g_num)) def add2(num): global g_num for i in range(num): mutex.acquire() g_num +=1 mutex.release() print("---add2---%s", str(g_num)) g_num = 0 # 创建锁(即创建Lock类的对象) mutex = threading.Lock() def main(): t1 = threading.Thread(target=add1) # 注意 使用了 args 参数 t2 = threading.Thread(target=add2, args=(1000000,)) t1.start() t2.start() time.sleep(2) print("---main---%s", str(g_num)) if __name__ == "__main__": main()
- 死锁现象
- 死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象。
- 若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
- 避免死锁现象:
- 程序设计时要尽量避免(银行家算法)
- 添加超时时间等。