04.python-多线程
介绍
线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程中,是进程中的实际运作单位。一个进程中可以并发多个线程,每条线程并行执行不同的任务。同一进程中的多个线程共享进程中的全部系统资源。
python中的多线程因为全局解释器锁GIL的原因限制同一时刻只能由一个线程运行,无法发挥多核CPU的优势【当然这是在默认解释器CPython中的缺陷,在JPython中没有GIL的问题,以下基于CPython解释器】。所以:GIL并不是python的特性,python也不依赖于GIL。说了那么多,那么python中的多线程是不是没用了?
答案当然不是。这取决于线程执行的场景是什么?是做计算(计算密集型)还是输入输出(I/O密集型),针对不同的场景使用不同的方法。多核心CPU可以有多个核心并行完成计算,所以多核提升的是计算性能,但每个CPU一旦遇到I/O阻塞,仍然需要等待,所以多核对I/O密集型任务没有太大的提升,多线程适合处理I/0密集型程序,如文件读写,web请求,数据库请求等。
使用
以下演示使用多线程对一个变量值进行修改,在循环的次数不多时修改后变量的值是符合预期的,当增加循环次数后,变量最终的值并不符合预期。由此可见:线程之间资源是存在竞争的,修改同一份资源必须加互斥锁,同时需要避免死锁。
# coding=utf-8
import threading
# 定义一个字段。多线程执行+1操作
balance = 0
def worker1():
global balance
for i in range(1000):
balance += 1
print('线程1执行完成,balance='+str(balance))
def worker2():
global balance
for i in range(1000):
balance += 1
print('线程2执行完成,balance='+str(balance))
def main():
# 构造线程对象
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
# 开始执行
t1.start()
t2.start()
"""
循环次数为1000时,程序输出:
线程1执行完成,balance=1000
线程2执行完成,balance=2000
循环次数为1000000时,程序输出:
线程1执行完成,balance=1180919
线程2执行完成,balance=1179703
"""
if __name__ == '__main__':
main()
要想解决以下的问题,需要使用线程的锁对象,只需要对worker1和woker2方法进行修改。
# 创建一个互斥锁,默认是未锁定状态
mutex = threading.Lock()
def worker1():
global balance
for i in range(1000000):
mutex.acquire()
balance += 1
mutex.release()
print('线程1执行完成,balance=' + str(balance))
def worker2():
global balance
for i in range(1000000):
mutex.acquire()
balance += 1
mutex.release()
print('线程2执行完成,balance=' + str(balance))
"""
加了互斥锁之后的输出:
线程1执行完成,balance=1941343
线程2执行完成,balance=2000000
"""
特点:
- 线程执行的顺序是不确定的
- 主线程【进程】会等待所有子线程结束后才会退出,主线程【进程】结束么子线程必然结束
- 线程间共享资源
- 修改资源必要时需要加锁,同时避免死锁
- 占用的资源比进程少
- 线程并不是越多越快
- 由于GIL的原因,多线程并不是真正的并发,只是交替执行
本作品采用《CC 协议》,转载必须注明作者和本文链接