总目录:https://www.jianshu.com/p/e406a9bc93a9
Python - 子目录:https://www.jianshu.com/p/50b432cb9460
多线程
我们先看一个简单的实例来查看一下我们电脑的线程:
import threading as td
def main():
#查看计算机有几个线程
print(td.active_count())
#这几个线程的名字
print(td.enumerate())
#查看运行这个程序的线程
print(td.current_thread())
if __name__ =='__main__':
main()
1
[<_MainThread(MainThread, started 10032)>]
<_MainThread(MainThread, started 10032)>
之后我们加一条线程:
import threadingas td
def td_job():
print('This is an added Thread,number is %s'%td.current_thread())
def main():
print('This is the original thread. The number is %s' % td.current_thread())
add_td = td.Thread(target=td_job)
#开始线程
add_td.start()
if __name__ =='__main__':
main()
This is the original thread. The number is <_MainThread(MainThread, started 19392)>
This is an added Thread,number is
我们增加了一条线程,但是这个线程的执行究竟是和主线程并发执行还是并行执行呢?
我们修改一下这个实例:
import threadingas td
import time
def td_job():
print("T1 start")
#等待1秒
time.sleep(1)
print("T1 finish")
def main():
add_td = td.Thread(target=td_job)
#开始线程
add_td.start()
print('all done')
if __name__ =='__main__':
main()
T1 start
all done
T1 finish
在输出过程中,T1 start和all done 同时出现,过去一秒后T1 finish才出现,这表示这两条线程的执行时并行执行。
如果这样的话,两条线程同时执行有些混乱,那么可不可以等待子线程先执行完再执行主线程呢。
import threadingas td
import time
def td_job():
print("T1 start")
#等待1秒
time.sleep(1)
print("T1 finish")
def main():
add_td = td.Thread(target=td_job)
#开始线程
add_td.start()
#等待子进程运行完
add_td.join()
print('all done')
if __name__ =='__main__':
main()
T1 start
T1 finish
all done
这样就会先把子线程运行完再执行主线程。
下面我们再添加一个子线程:
import threadingas td
import time
def t1_job():
print("T1 start")
time.sleep(2)
print("T1 finish")
def t2_job():
print("T2 start")
time.sleep(1)
print("T2 finish")
def main():
add_t1 = td.Thread(target=t1_job,name='T1')
#开始线程
add_t1.start()
#等待子进程运行完
add_t1.join()
add_t2 = td.Thread(target=t2_job,name='T2')
#开始线程
add_t2.start()
#等待子进程运行完
add_t2.join()
print('all done')
if __name__ =='__main__':
main()
T1 start
T1 finish
T2 start
T2 finish
all done
这个程序我们来更改一下几个进程的运行顺序。
情况1:
如果T1,T2和主进程同时执行
add_t1 = td.Thread(target=t1_job,name='T1')
#开始线程
add_t1.start()
add_t2 = td.Thread(target=t2_job,name='T2')
#开始线程
add_t2.start()
print('all done')
T1 start
T2 start
all done
T2 finish
T1 finish
T2比T1先执行完成是因为T2执行了1s,而T1执行了2s。
情况2:
对T2进行join操作。
add_t1 = td.Thread(target=t1_job,name='T1')
#开始线程
add_t1.start()
add_t2 = td.Thread(target=t2_job,name='T2')
#开始线程
add_t2.start()
#等待子进程运行完
add_t2.join()
print('all done')
T1 start
T2 start
T2 finish
all done
T1 finish
这样主线程和T1就会先等T2执行完成后再输出。
线程队列
如果我们有很多个线程,但是这些线程都在重复执行一个函数,那么我们需要一个东西来保持我们线程的顺序,这个东西就是队列。
import threading as td
import time
from queue import Queue
#我们定义一个任务方法,他会接受一个列表和一个队列,将列表处理的结果put到队列中。
def job(l,q):
for i in range(len(l)):
l[i] = l[i]**2
q.put(l)
#线程方法,定义了一个有四个列表元素的列表,生产了四个线程,线程会同时执行job方法,将结果保存到q队列中,之后遍历队列取出结果。
def mul():
q = Queue()
threads = []
data = [[1,2,3],[4,5,6],[7,8,9],[1,3,5]]
for i in range(4):
t = td.Thread(target=job,args=(data[i],q))
t.start()
threads.append(t)
print(threads)
for threadin threads:
thread.join()
results = []
for _in range(4):
results.append(q.get())
print(results)
if __name__ =="__main__":
mul()
[
, , , ] [[1, 4, 9], [16, 25, 36], [49, 64, 81], [1, 9, 25]]
之后我们来看一个东西,这个东西就是Python的一个诟病,全局解释器锁,既GIL。
GIL
我们从三个方面看GIL:什么是GIL,他的执行流程,他对多线程的影响。
什么是GIL
GIL 是CPython 解释器中的一个技术术语,中文译为全局解释器锁,其本质上类似操作系统的 Mutex。
GIL 的功能是在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
接着我们在说他对工作原理前,说说他对多线程的影响,我们来看两个程序:
单线程程序:
import time
start = time.clock()
def CountDown(n):
while n >0:
n -=1
CountDown(100000)
print("Time used:",(time.clock() - start))
Time used: 0.004817182669318798
双线程程序:
import time
from threadingimport Thread
start = time.clock()
def CountDown(n):
while n >0:
n -=1
t1 = Thread(target=CountDown, args=[100000 //2])
t2 = Thread(target=CountDown, args=[100000 //2])
t1.start()
t2.start()
t1.join()
t2.join()
print("Time used:",(time.clock() - start))
Time used: 0.005911790958890454
为什么双线程的执行速度还没有单线程块呢?
我们就要来看GIL的执性流程了。
GIL的执行流程
上面这张图,就是 GIL 在 Python 程序的工作示例。其中,Thread 1、2、3 轮流执行,每一个线程在开始执行时,都会锁住 GIL,以阻止别的线程执行;同样的,每一个线程执行完一段后,会释放 GIL,以允许别的线程开始利用资源。
如果仅仅要求 Python 线程在开始执行时锁住 GIL,且永远不去释放 GIL,那别的线程就都没有运行的机会。
其实,CPython 中还有另一个机制,叫做间隔式检查(check_interval),意思是 CPython 解释器会去轮询检查线程 GIL 的锁住情况,每隔一段时间,Python 解释器就会强制当前线程去释放 GIL,这样别的线程才能有执行的机会。
接下来再看一个程序,看一下在什么适当的情况下GIL会释放。
import threading
count =0
def add():
global count
for iin range(10 **6):
count +=1
def minus():
global count
for iin range(10 **6):
count -=1
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=minus)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(count)
我们执行三次:
-455479
-107294
-134711
可以看到count并不是一个固定值,说明GIL会在某个时刻释放,那么GIL具体在什么情况下释放呢:
1.执行的字节码行数到达一定阈值
2.通过时间片划分,到达一定时间阈值
3.在遇到IO操作时,主动释放
GIL不能绝对保证线程安全
import threading
n =0
def foo():
global n
n +=1
threads = []
for iin range(100):
t = threading.Thread(target=foo)
threads.append(t)
for tin threads:
t.start()
for tin threads:
t.join()
print(n)
如果线程绝对安全,不管执行多少次,这个程序的结果都是100,但是他的结果有可能是99,甚至是98,这就表示有进程丢失了。
我们最后来看一下线程中的锁。
lock
我们先看一个例子
import threadingas td
def job1():
global A
for iin range(10):
A +=1
print('job1',A)
def job2():
global A
for iin range(10):
A +=10
print('job2',A)
if __name__ =='__main__':
A =0
t1 = td.Thread(target=job1)
t2 = td.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
#在命令行执行
我们可以看到,输出结果很乱,job1的输出还和job2的输出产生了叠加,这显然不是我们想要的,所以就有了锁--lock。
import threadingas td
def job1():
global A,lock
#上锁
lock.acquire()
for iin range(10):
A +=1
print('job1',A)
#开锁
lock.release()
def job2():
global A,lock
#上锁
lock.acquire()
for iin range(10):
A +=10
print('job2',A)
#开锁
lock.release()
if __name__ =='__main__':
#产生锁
lock = td.Lock()
A =0
t1 = td.Thread(target=job1)
t2 = td.Thread(target=job2)
t1.start()
t2.start()
t1.join()
t2.join()
这样结果就很nice了。
锁就是保证线程的安全,不会发生线程冲突。