线程:
线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行
并发与并行:
单核CUP执行多任务,由于CUP执行代码都是顺序执行的,当执行多个任务的时候操作系统会轮流让各个任务交替执行,由于执行速度过快,感觉是所有的任务同时执行一样,称为并发,当任务数小于CUP数量时,这是任务是真正的在一起运行,称为并行。
同步与异步:
同步(协同步调):线程在访问某 一资源时,获得了资源的返回结果后才会执行其他操作。
异步(步调各异):与同步相对,在访问某一资源时,无论资源是否有返回结果,都会进行下一步操作,当资源有结果时,系统会自动通知线程。
举例:创建两个普通函数
import time
def fun():
for i in range(3):
time.sleep(1)
print('fun:', i)
def fun1():
for i in range(3):
time.sleep(1)
print('fun1:', i)
st = time.time()
fun()
fun1()
et = time.time()
print("运行时间:", et - st)
运行结果:
fun: 0
fun: 1
fun: 2
fun1: 0
fun1: 1
fun1: 2
运行时间: 6.063438653945923
Threading模块
通过多线程对多任务的处理,通过threading中的Thread模块。如下通过Thread创建一个线程对象,将需要运行的函数名t赋值给target,调用对象的start方法来启动线程。
import time
from threading import Thread
def fun():
for i in range(3):
time.sleep(1)
print('fun:', i)
def fun1():
for i in range(3):
time.sleep(1)
print('fun1:', i)
st = time.time()
fun = Thread(target=fun) # 创建线程对象
fun.start() # 启动线程
fun1()
et = time.time()
print("运行时间:", et - st)
结果:
fun: 0
fun1: 0
fun1: 1
fun: 1
fun: 2
fun1: 2
运行时间: 3.0180318355560303
如果fun1也通过创建线程的方式来启动,来看下效果:
import time
from threading import Thread
def fun():
for i in range(3):
time.sleep(1)
print('fun:', i)
def fun1():
for i in range(3):
time.sleep(1)
print('fun1:', i)
st = time.time()
fun = Thread(target=fun) # 创建线程对象
fun1 = Thread(target=fun1) # 创建线程对象
fun.start() # 启动线程
fun1.start() # 启动线程
et = time.time()
print("运行时间:", et - st) # 主线程
结果:
运行时间: 0.0009970664978027344
fun: 0
fun: 1
fun: 2
fun1: 0
fun1: 1
fun1: 2
这时候主线程并print并没有等待子线程运行结束,现有需求需要等待子线程执行结束后才运行主线程,可以用join()方法来控制等待子线程运行的时间,默认是等待子线程运行结束。
举例:
import time
from threading import Thread
def fun():
for i in range(3):
time.sleep(1)
print('fun:', i)
def fun1():
for i in range(3):
time.sleep(1)
print('fun1:', i)
st = time.time()
fun = Thread(target=fun) # 创建线程对象
fun1 = Thread(target=fun1) # 创建线程对象
fun.start() # 启动线程
fun1.start() # 启动线程
fun.join() # 等待fun线程运行结束,fun.join(2) 等待2秒
et = time.time()
print("运行时间:", et-st)
结果:
fun: 0
fun1: 0
fun1: 1
fun: 1
fun: 2
fun1: 2
运行时间: 3.0180318355560303
任务函数有参数时
方式一:元组的形式赋值给args
import time
from threading import Thread
def fun(name,age):
for i in range(3):
time.sleep(1)
print(f'fun:{name},{age}', i)
fun = Thread(target=fun, args=('MING',19)) # 创建线程对象,函数名赋值给args
fun.start()
结果:
fun:MING,19 0
fun:MING,19 1
fun:MING,19 2
方式二:字典的方式赋值给kwargs
import time
from threading import Thread
def fun(name,age):
for i in range(3):
time.sleep(1)
print(f'fun:{name},{age}', i)
fun = Thread(target=fun, kwargs={'name': 'MING', 'age': 19}) # 创建线程对象
fun.start()
结果:
fun:MING,19 0
fun:MING,19 1
fun:MING,19 2
自定义线程类
重写线程类的run方法,在run方法中写线程执行的具体代码,调用start方法时会默认执行run下面的代码。
import time
from threading import Thread
class MyThread(Thread):
def __init__(self,name):
super().__init__() # 重写init方法
self.name = name
def run(self):
for i in range(10):
time.sleep(1)
print(f'{self.name}请求第{i}次')
if __name__ == '__main__':
for i in range(5):
t = MyThread("MING")
t.start()
多线程共享全局变量
from threading import Thread
a = 0
def fun():
global a
for i in range(100):
a += 1
print("fun:", a)
def fun1():
global a
for i in range(100):
a += 1
print("fun1:", a)
if __name__ == '__main__':
Thread(target=fun).start()
Thread(target=fun1).start()
>>> fun: 100
>>> fun1: 200
多线程是无法利用多核CPU的资源,在同一时间,只能有一个线程在执行,因为python解释器有把GIL(Global Interpreter Lock)全局解释器锁。
如图:当线程想要执行,必须先拿到GIL,可以把GIL看作是“通行证”,并且在一个python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。GIL只在cpython中才有,因为cpython调用的是c语言的原生线程,所以他不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。
而当共享全局变量的时候则会出现新的问题,python线程执行的切换机制:
1.线程执行遇到IO耗时操作,比如sleep,文件的输入输出,网络请求等。
2.线程执行时间达到一个阈值后
当启动多个线程同时,引用的全局变量会造成资源竞争而导致数据不准确
如下:
from threading import Thread
a = 0
def fun():
global a
for i in range(100000):
a += 1
print("fun:", a)
def fun1():
global a
for i in range(100000):
a += 1
print("fun1:", a)
if __name__ == '__main__':
Thread(target=fun).start()
Thread(target=fun1).start()
print('a:',a)
>>> fun:100000
>>> fun1:200000
>>> a: 165424
互斥锁:Lock类
而解决上面的问题可以通过引入互斥锁,状态为锁定/非锁定,当某个线程更改共享数据时,先将其锁定,此时资源的状态为 “锁定” ,其他线程不能更改直到该线程释放资源,将资源的状态变为 ”非锁定“ , 其他线程才能再次锁定该资源。
l = threading.Lock() #创建锁
l.acquire() # 锁定
l.release() # 释放
举例:
from threading import Thread, Lock
a = 0
lock = Lock() # 创建锁
def fun():
global a
for i in range(100000):
lock.acquire() # 上锁
a += 1
lock.release() # 释放锁
print("fun:", a)
def fun1():
global a
for i in range(100000):
lock.acquire() # 上锁
a += 1
lock.release() # 释放锁
print("fun1:", a)
if __name__ == '__main__':
t = Thread(target=fun)
t.start()
t1 = Thread(target=fun1)
t1.start()
t.join()
t1.join()
print('a:',a)
>>> fun:154229
>>> fun1:165424
>>> a: 200000
线程池:ThreadPoolExecutor
通过ThreadPoolExecutor 创建线程池,submit提交任务到线程池。max_workers指定线程池中的数量
import time
from concurrent.futures import ThreadPoolExecutor
def work(name):
for i in range(5):
time.sleep(1)
print(f"{name}----{i}----")
with ThreadPoolExecutor(max_workers=4) as tp: # 创建线程池
tp.submit(work, 'MING') # 往线程提交任务
tp.submit(work, 'HOUX')
tp.shutdown() # 等待线程池中所有的任务执行完成
print("---主线程---")
运行结果:
MING----0----HOUX----0----
MING----1----HOUX----1----
HOUX----2----MING----2----
MING----3----HOUX----3----
HOUX----4----MING----4----
---主线程---