多线程
- 描述
多线程类似于同时执行多个不同程序
- 优点
a.使用线程可以把占据长时间的程序中的任务放到后台去处理
b.用户界面可以更加吸引人,比如用户点击一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
c.程序的运行速度可能加快
d.在一些等待的任务实现上如用户输入。文件读写和网络收发数据等,线程就比较有用。
- 线程种类
内核线程:由操作系统内核创建和撤销
用户线程:不需要内核支持而在程序中实现的线程
- 线程模块
_thread:低级模块
threading:高级模块(推荐使用)
- threading方法
run():用以表示线程活动的方法
start():启动线程活动
join([time]):等待至线程中止。这堵塞调用线程直至线程的join()方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生
isAlive():返回线程是否活动的
getName():返回线程名
setName():设置线程名
例
import time
import threading
# 新线程执行的代码
def loop():
print('thread {} is running...'.format(threading.current_thread().name))
n = 0
while n < 5:
n = n + 1
print('thread {} >>> {}'.format(threading.current_thread().name, n))
time.sleep(1)
print('thread {} ended'.format(threading.current_thread().name))
print('thread {} is running...'.format(threading.current_thread().name))
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread {} ended.'.format(threading.current_thread().name))
输出结果
thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended
thread MainThread ended.
由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程。
Python的threading
模块有个current_thread()
函数,他永远返回当前线程的实例。
主线程实例的名字叫MainThread
,子线程的名字在创建时指定,用LoopThread
命名子线程。名字仅仅在打印时用来显示,没有其他意义。
- 线程同步
多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享。
为了保证数据的正确性,需要对多个线程进行同步
多线程该乱变量案例
import time
import threading
# 假定银行存款
balance = 0
def change_it(n):
# 先存后取,结果应该为0
global balance
balance = balance + n
balance = balance - n
def run_thread(n):
for i in range(100000):
change_it(n)
t1 = threading.Thread(target=run_thread, args=(5,))
t2 = threading.Thread(target=run_thread, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(balance)
共享变量balance
,初始值为0
,启动两个线程,先存后取,理论结果应该为0
,但由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果不一定为0
高级语言的一条语句在CPU执行时时若干条语句
balance = balance + n
分为两部
1.计算balance + n
,存入临时变量中
2.将临时变量的值赋给balance
相当于
x = balance + n
balance = x
由于x是局部变量,两个线程各自都有自己的x
代码正常执行时
初始值 balance = 0
t1 : x1 = balance + 5 # x1 = 0 + 5 = 5
t1 : balance = x1 # balance = 5
t1 : x1 = balance - 5 # x1 = 5 - 5 = 9
t1 : balance = x1 # balance = 0
t2 : x2 = banlance + 8
t2 : balance = x2
t2 : x2 = balance - 8
t2 : balance = x2
结果:balance = 0
但t1和t2诗交替运行的,如果操作系统以下面的顺序执行t1、t2
初始值 balance = 0
t1 : x1 = balance + 5 # x1 = 0 + 5 = 5
t2 : x2 = balance + 8 # x2 = 0 + 8 = 8
t2 : balance = x2 # balance = 8
t1 : balance = x1 # balance = 5
t1 : x1 = balance - 5 # x1 = 5 - 5 = 0
t1 : balance = x1 # balance = 0
t2 : t2 = balance - 8 # x2 = 0 - 8 = -8
t2 : banlance = x2 # balance = -8
结果:balance = -8
因为修改balance
需要多条语句,而执行官这条语句时,线程可能中断,从而导致多个线程把一同一个对象的内容改乱了。
为了确保balance
计算正确,需要给change_it()
上一把锁,当线程开始执行change_it()
时,该线程获得了锁,其他线程便不能同时执行change_it()
,只能等待,直到锁被释放后,获得该锁以后才能修改。
通过threading.Lock()
来创建锁
balance = 0
lock = threading.Lock()
def run_thread(n):
for i in range(1000000):
# 先要获取锁
lock.acquire()
try:
# 放心地修改吧
change_it(n)
finally:
# 改完了一定要释放锁
lock.release()
当多个线程同时执行lock.acquire()
时,只有一个线程能成功获取锁,然后继续执行代码,其他线程就继续等待直到获取锁为止。
获得锁的线程用完后一定要释放锁,否则等待锁的线程将永远等待下去,成为死线程。用try...finally
来确保锁一定会被释放。
- GIL全局锁(Global Interpreter Lock)
任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。
由于GIL全局锁的存在,多线程在Python中只能交替执行,在多核CPU上,也只能用到1个核