串行
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。是串行,必须执行完一个执行一个。
并发
你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发,交叉执行。
并行
你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行,同时执行。
并发的关键是你有处理多个任务的能力,不一定要同时。
并行的关键是你有同时处理多个任务的能力
多任务
同时干多件事, 有几核cpu就能同时干几件事
线程
最小的调度单位,线程是并发的,
多线程
多线程类似于同时执行多个不同程序,多线程运行有如下特点:
1.无序性
2.线程可以被抢占(中断)
3.在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
4.线程的并发是利用cpu上下文的切换
5.多线程开销小,比较适合在IO密集的代码里使用。
要设置多线程,需要调用模块
import threading
实现多线程:
import threading #调用多线程模块 import time def test1(): for i in range(10): time.sleep(1) print('test1=========>%s' % i) def test2(): for i in range(10): time.sleep(1) print('test2=========>%s' % i) t1 = threading.Thread(target=test1) #开启一个多线程将test1函数传入并起别名 t2 = threading.Thread(target=test2) t1.start() #开启t1线程 t2.start() test1() test2()
执行结果
执行顺序,——> test1 ——> test2 ——> test1 ——> test2 ——> ....
test2=========>0 test1=========>0 test2=========>1 test1=========>1 test2=========>2 test1=========>2 test1=========>3 test2=========>3 test2=========>4 test1=========>4
证明多线程的无序性
def test1(n): time.sleep print('task', n) for i in range(5): t = threading.Thread(target=test1,args=('t-%s' % i,)) 给test1函数传入实参('t-%s' % i,),括号内最后的’,’不可缺少 t.start()
结果:无序
task t-1 task t-3 task t-0 task t-2 task t-4
多线程共享全局变量
全局变量:
这是因为,在一个作用域里面给一个变量赋值的时候,Python自动认为这个变量是这个作用域的本地变量,并屏蔽作用域外的同名的变量。 所以我们会得到这样 UnboundLocalError 的一个异常。
使用全局变量的话不需要声明,给全局变量赋值的话就需要声明了。 一般全局变量在一个函数中是不可以调用的,但是声明的话,就可以在函数中调用全局函数
globa 变量名 :声明全局变量
num = 0 def update(): global num #global声明全局变量 for i in range(10): num += 1 def reader(): global num print(num) t1 = threading.Thread(target=update) t2 = threading.Thread(target=reader) t1.start() t2.start()
结果
10
GIL全局解释器锁
只要在进行耗时的IO操作的时候,能释放GIL, 所以只要在IO密集型的代码里,用多线程就很合适
import threading global_num = 0 def test1(): global global_num for i in range(1000000): global_num += 1 print("test1", global_num,threading.current_thread()) #打印当前线程的信息 def test2(): global global_num for i in range(1000000): global_num += 1 print("test2", global_num,threading.current_thread()) t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() t2.start() # t1.join() # 执行完t1在执行主函数 # t2.join() # 执行完t2再执行主函数 print(global_num)
结果:
如果注释掉 t1.join(), t2.join() 这两行,结果:(每一次都不一样)
472178 test1 1151965 <Thread(Thread-1, started 14300)> test2 1279527 <Thread(Thread-2, started 4348)>
如果加上 t1.join(), t2.join() 这两行,结果:(每次都不一样)
主函数的结果和最后执行完的线程的结果一样
test1 1087118 <Thread(Thread-1, started 13744)> test2 1320500 <Thread(Thread-2, started 19292)> 1320500
互斥锁
说到gil解释器锁,我们容易想到在多线程中共享全局变量的时候会有线程对全局变量进行的资源竞争,会对全局变量的修改产生不是我们想要的结果,而那个时候我们用到的是python中线程模块里面的互斥锁,哪样的话每次对全局变量进行操作的时候,只有一个线程能够拿到这个全局变量;看下面的代码:
import threading import time global_num = 0 lock = threading.Lock() def test1(): global global_num lock.acquire() for i in range(1000000): global_num += 1 lock.release() print("test1", global_num) def test2(): global global_num lock.acquire() for i in range(1000000): global_num += 1 lock.release() print("test2", global_num) t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) start_time = time.time() t1.start() t2.start()
结果
互斥性:不可分割,要不不做,要做做完。
加上锁之后就变成了串行,加锁的范围尽量精确,范围要缩到最小
test1 1000000 test2 2000000