Python多线程
18.1 线程与进程的关系
程序是一些列的指令集,程序是静态的,当程序运行时,就会创建一个进程。
线程是进程的基本执行单元,一个进程至少要具有一个线程。
18.2 多线程
多线程,就类似与操作系统中的多进程。简单的讲,就是可以同时并发执行多个任务,处理多件事情。
18.2.1 计算密集与IO密集
计算密集,是指在程序运行中,更多的时间为CPU在进行计算。而IO密集,则是更多的时间,在进行输入输出。
Python中,threading模块提供了关于线程的相关操作。
18.2.2 线程的创建
我们可以采用两种方式来创建线程:
- 使用threading模块的Thread类,通过指定target与args(可选)参数。
- 通过继承Thread类,重写run方法。
思考:直接调用run与调用start方法有什么不同?
线程的生命周期:
线程的生命周期可以分为以下环节:
- 新建:创建对象
- 就绪:调用start后
- 运行:获得CPU资源
- 阻塞(挂起):失去CPU资源
- 死亡:线程执行结束或抛出未捕获的异常
18.2.3 线程相关操作
threading模块相关功能:
threading.active_count()
threading.enumerate()
threading.current_thread()
threading.get_ident()
threading.main_thread()
线程对象功能:
start()
run()
join(timeout=None)
name setName / getName
ident
is_alive()
daemon isDaemon() / setDaemon()
18.3 线程同步
18.3.1 并发修改的问题
当多线程并发运行时,多线程间很可能操作共享成员变量,此时,就需要对共享成员变量的操作进行同步,避免出现多线程的并发修改而引起的意外错误。
18.3.2 线程锁
我们可以通过threading.Lock()获得线程锁,来实现多个线程对共享区域的互斥访问。线程锁提供两个方法:
- acquire():尝试去获得锁。
- release():释放所获得的锁。
注意:要确保锁得到有效的释放。
18.3.3 死锁
当两个或多个线程同时拥有自己的资源,而相互等待获得对方资源,导致程序永远陷入僵持状态,这就是死锁。
当多线程并发访问共享数据时,使用线程锁可以避免多线程对共享变量并发修改带来的危害,但同时有可能会产生死锁。
18.4 线程队列
queue模块提供了队列的功能,该模块具有三个类:
- queue.Queue(maxsize=0)
- queue.LifoQueue(maxsize=0)
- queue.PriorityQueue(maxsize=0)
队列类实现了内部的锁机制,因此,队列类型可以安全的用于多线程并发操作中。
其中,参数maxsize表示队列的最大元素个数,如果传递0或者负值,则表示无限容量。如果队列达到了最大容量,将会进行阻塞,直到有元素删除为止。
队列对象具有的方法如下:
qsize()
empty()
full()
put(item, block=True, timeout=None)
put_nowait(item) put(item, False)
get(block=True, timeout=None)
get_nowait() get(False)
18.5 习题
1.当创建一个线程时,该线程是前台线程还是后台线程?用代码验证一下。
2.编写两个线程,对同一个全局变量增加若干次(次数多一点),会出现什么情况。
3.两个线程,使用同一个函数作为target,然后在函数定义一个局部变量,两个线程分别对该变量自增若干次,会出现什么情况。
4.编写买家与卖家交易的程序,一个钱锁,一个货锁,并造成死锁。