GIL:全局解释器锁,
# 作用:只允许一个线程通过,所有Python中的多线程是假的
# 进程:系统分配的一个资源单位
# 线程:进程中的一个分支,进程中至少有一个主线程
# 多线程: 多个线程并发的一种技术
# 同步:按顺序执行
# 异步:可以理解为在不同的线程中独立执行
# 并行:任务数 <= CPU数
# 并发: 任务数 > CPU数
#
# 线程锁/互斥锁: threading.Lock,解决线程冲突
# 死锁:多个线程同一时刻占用资源不愿意释放,又想占用对方的资源,造成相互等待的现象
# 递归锁:threading.RLock, 解决死锁
#
# 信号量:控制线程的最大并发数
线程
在一个进程的内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”叫做线程。
线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务,每一个线程都共享一个进程的资源。
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,执行多长时间。
模块 1、_thread模块 低级模块,接近底层。
线程基本用法
基本跟进程类似。
from threading import Thread, current_thread
import time
def run(num):
print('子线程(%s)开始' % (current_thread().name,))
time.sleep(2)
print('打印', num)
time.sleep(2)
print('子线程(%s)结束' % (current_thread().name,))
if __name__ == '__main__':
# 任何进程默认就会启动一个线程,称为主线程,主线程可以启动新的子线程
# current_thread():返回返回当前线程的实例
print('主线程(%s)开始' % (current_thread().name,))
# 创建子线程
t = Thread(target=run, args=(1,), name='runThread')
t.start()
# 等待线程结束
t.join()
print("主线程(%s)结束" % (current_thread().name))
练习: 将上述新建线程的代码改成使用继承Thread类的方式。
常见方法
方法名 | 说明 |
---|---|
isAlive() | 返回线程是否在运行。正在运行指启动后、终止前。 |
get/setName(name) | 获取/设置线程名。 |
start() | 线程准备就绪,等待CPU调度 |
is/setDaemon(bool) | 获取/设置是守护线程(默认前台线程(False))。(在start之前设置) |
join([timeout]) | 阻塞当前上下文环境的线程,直到调用此方法的线程终止或到达指定的timeout(可选参数) |
线程间共享数据
线程数据是共享的。先来看一个简单的例子。
from threading import Thread
from time import sleep
# 全局数据
num = 100
def run():
print('子线程开始')
global num
num += 1
print('子线程结束')
if __name__ == '__main__':
print('主线程开始')
# 创建主线程
t = Thread(target=run)
t.start()
t.join()
print(num)
print('主线程结束')
执行结果:
主线程开始
子线程开始
子线程结束
101
主线程结束
可以看到子线程修改的全局变量,在主线程中也体现出来了。
线程之间数据共享引发的问题
多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在每个进程中,互不影响。而多线程中,所有变量都由所有线程共享。所以,任何一个变量都可以被任意一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时修改一个变量,容易把内容改乱了。
下面来模拟这个问题:
from threading import Thread
# 全局变量
num = 100
def run(n):
global num
for i in range(100000000):
num = num + n
num = num - n
if __name__ == '__main__':
t1 = Thread(target=run, args=(6,))
t2 = Thread(target=run, args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print('num=',num)
运行之后发现num不等于100了。这就是因为两个线程都对num进行操作,中间发生了紊乱。
使用线程锁解决数据混乱问题
threading中的Lock类表示锁。
锁确保了这段代码只能由一个线程从头到尾的完整执行。阻止了多线程的并发执行,包含锁的某段代码实际上只能以单线程模式执行,所以效率大大滴降低了。
由于可以存在多个锁,不同线程持有不同的锁,并试图获取其他的锁,可能造成死锁,导致多个线程挂起。只能靠操作系统强制终止。
我们给之前的代码加上锁 来解决数据混乱的问题。
from threading import Thread, Lock
# 全局变量
num = 100
# 锁对象
lock = Lock()
def run(n):
global num
global lock
for i in range(100000000):
# 获取锁
lock.acquire()
try:
num = num + n
num = num - n
finally:
# 修改完一定 要释放锁
lock.release()
if __name__ == '__main__':
t1 = Thread(target=run, args=(6,))
t2 = Thread(target=run, args=(9,))
t1.start()
t2.start()
t1.join()
t2.join()
print('num=',num)
使用上下文管理器 with,可以自动获取锁,释放锁。可以将上面代码的try语句改成如下:
with lock:
num = num + n
num = num - n