Python多线程Threading学习笔记

来源:互联网 发布:手机淘宝怎么引流 编辑:程序博客网 时间:2024/05/17 07:27

参考:https://morvanzhou.github.io/tutorials/python-basic/threading/

1 添加线程

通过print(threading.active_count())可以显示有几个线程在运行

通过print(threading.enumerate())可以显示运行线程的名字

通过print(threading.current_thread())可以显示正在运行的程序是哪个线程

添加线程的方法:add_thread = threading.Thread(target = thread_job),其中thread_job定义了线程自定义的线程中要做的工作

然后add_thread.start()开始运行添加的线程

def thread_job():

……

2 join功能

如果想要等所有线程运行结束后再运行主线程中的程序时,需要添加add_thread.join(),这样就会有一个先后的顺序,而不是都是同时进行

(1)不加 join() 的结果
我们让 T1 线程工作的耗时增加.
import threading
import time
def thread_job():
    print("T1 start\n")
    for i in range(10):
        time.sleep(0.1) # 任务间隔0.1s
    print("T1 finish\n")
added_thread = threading.Thread(target=thread_job, name='T1')
added_thread.start()
print("all done\n")
预想中输出的结果是否为:
T1 start
T1 finish
all done
但实际却是:
T1 start
all done
T1 finish
(2)加入 join() 的结果
线程任务还未完成便输出all done。如果要遵循顺序,可以在启动线程后对它调用join:
added_thread.start()
added_thread.join()
print("all done\n")
使用join对控制多个线程的执行顺序非常关键。举个例子,假设我们现在再加一个线程T2,T2的任务量较小,会比T1更快完成:
def T1_job():
    print("T1 start\n")
    for i in range(10):
        time.sleep(0.1)
    print("T1 finish\n")
def T2_job():
    print("T2 start\n")
    print("T2 finish\n")
thread_1 = threading.Thread(target=T1_job, name='T1')
thread_2 = threading.Thread(target=T2_job, name='T2')
thread_1.start() # 开启T1
thread_2.start() # 开启T2
print("all done\n")
输出的”一种”结果是:
T1 start
T2 start
T2 finish
all done
T1 finish
现在T1和T2都没有join,注意这里说”一种”是因为all done的出现完全取决于两个线程的执行速度, 完全有可能T2 finish出现在all done之后。这种杂乱的执行方式是我们不能忍受的,因此要使用join加以控制。
(3)我们试试在T1启动后,T2启动前加上thread_1.join():
thread_1.start()
thread_1.join() # notice the difference!
thread_2.start()
print("all done\n")
输出结果:
T1 start
T1 finish
T2 start
all done
T2 finish
可以看到,T2会等待T1结束后才开始运行。
(4)如果我们在T2启动后放上thread_1.join()会怎么样呢?
thread_1.start()
thread_2.start()
thread_1.join() # notice the difference!
print("all done\n")
输出结果:
T1 start
T2 start
T2 finish
T1 finish
all done
T2在T1之后启动,并且因为T2任务量小会在T1之前完成;而T1也因为加了join,all done在它完成后才显示。
(5)你也可以添加thread_2.join()进行尝试,但为了规避不必要的麻烦,推荐如下这种1221的V型排布:
thread_1.start() # start T1
thread_2.start() # start T2
thread_2.join() # join for T2
thread_1.join() # join for T1
print("all done\n")
"""
T1 start
T2 start
T2 finish
T1 finish
all done
"""

3 存储进程结果Queue

线程是不能返回值的,把多线程的运行结果存储起来,然后在主线程中进行调用

代码实现功能,将数据列表中的数据传入,使用四个线程处理,将结果保存在Queue中,线程执行完后,从Queue中获取存储的结果

(1)导入线程,队列的标准模块
import threading
import time
from queue import Queue
(2)定义一个被多线程调用的函数
函数的参数是一个列表l和一个队列q,函数的功能是,对列表的每个元素进行平方计算,将结果保存在队列中
def job(l,q):
    for i in range (len(l)):
        l[i] = l[i]**2
    q.put(l)   #多线程调用的函数不能用return返回值
(3)定义一个多线程函数
在多线程函数中定义一个Queue,用来保存返回值,代替return,定义一个多线程列表,初始化一个多维数据列表,用来处理:
def multithreading():
    q =Queue()    #q中存放返回值,代替return的返回值
    threads = []
    data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
(4)在多线程函数中定义四个线程,启动线程,将每个线程添加到多线程的列表中
for i in range(4):   #定义四个线程
    t = threading.Thread(target=job,args=(data[i],q)) #Thread首字母要大写,被调用的job函数没有括号,只是一个索引,参数在后面
    t.start()#开始线程
    threads.append(t) #把每个线程append到线程列表中
(5)分别join四个线程到主线程
for thread in threads:
    thread.join()
(6)定义一个空的列表results,将四个线运行后保存在队列中的结果返回给空列表results
results = []
for _ in range(4):
    results.append(q.get())  #q.get()按顺序从q中拿出一个值
print(results)
(7)完整的代码
import threading
import time
from queue import Queue
def job(l,q):
    for i in range (len(l)):
        l[i] = l[i]**2
    q.put(l)

def multithreading():
    q =Queue()
    threads = []
    data = [[1,2,3],[3,4,5],[4,4,4],[5,5,5]]
    for i in range(4):
        t = threading.Thread(target=job,args=(data[i],q))
        t.start()
        threads.append(t)
    for thread in threads:
        thread.join()
    results = []
    for _ in range(4):
        results.append(q.get())
    print(results)

if __name___=='__main__':
    multithreading()
最后运行结果为:

[[1, 4, 9], [9, 16, 25], [16, 16, 16], [25, 25, 25]]

4 GIL不一定有效率

Python只能在同一时间运行同一个线程,然后互相切换,实现多线程的效果,因此运行时间不是说一般的运行时间除以线程数

原因是GIL的作用,示意图如下


5 线程锁Lock

(1)不使用 Lock 的情况
函数一:全局变量A的值每次加1,循环10次,并打印
def job1():
    global A
    for i in range(10):
        A+=1
        print('job1',A)
函数二:全局变量A的值每次加10,循环10次,并打印
def job2():
    global A
    for i in range(10):
        A+=10
        print('job2',A)
主函数:定义两个线程,分别执行函数一和函数二
if __name__== '__main__':
    A=0
    t1=threading.Thread(target=job1)
    t2=threading.Thread(target=job2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
完整代码:
import threading
def job1():
    global A
    for i in range(10):
        A+=1
        print('job1',A)
def job2():
    global A
    for i in range(10):
        A+=10
        print('job2',A)
if __name__== '__main__':
    lock=threading.Lock()
    A=0
    t1=threading.Thread(target=job1)
    t2=threading.Thread(target=job2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
运行结果(在spyder编译器下运行的打印结果):
job1job2 11
job2 21
job2 31
job2 41
job2 51
job2 61
job2 71
job2 81
job2 91
job2 101
 1
job1 102
job1 103
job1 104
job1 105
job1 106
job1 107
job1 108
job1 109
job1 110
可以看出,打印的结果非常混乱
(2)使用 Lock 的情况
lock在不同线程使用同一共享内存时,能够确保线程之间互不影响,使用lock的方法是, 在每个线程执行运算修改共享内存之前,执行lock.acquire()将共享内存上锁, 确保当前线程执行时,内存不会被其他线程访问,执行运算完毕后,使用lock.release()将锁打开, 保证其他的线程可以使用该共享内存。
函数一和函数二加锁
def job1():
    global A,lock
    lock.acquire()
    for i in range(10):
        A+=1
        print('job1',A)
    lock.release()
def job2():
    global A,lock
    lock.acquire()
    for i in range(10):
        A+=10
        print('job2',A)
    lock.release()
主函数中定义一个Lock
if __name__== '__main__':
    lock=threading.Lock()
    A=0
    t1=threading.Thread(target=job1)
    t2=threading.Thread(target=job2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
完整的代码
import threading
def job1():
    global A,lock
    lock.acquire()
    for i in range(10):
        A+=1
        print('job1',A)
    lock.release()
def job2():
    global A,lock
    lock.acquire()
    for i in range(10):
        A+=10
        print('job2',A)
    lock.release()
if __name__== '__main__':
    lock=threading.Lock()
    A=0
    t1=threading.Thread(target=job1)
    t2=threading.Thread(target=job2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
运行结果
job1 1
job1 2
job1 3
job1 4
job1 5
job1 6
job1 7
job1 8
job1 9
job1 10
job2 20
job2 30
job2 40
job2 50
job2 60
job2 70
job2 80
job2 90
job2 100
job2 110
从打印结果来看,使用lock后,一个一个线程执行完。使用lock和不使用lock,最后打印输出的结果是不同的。