前言
之前做过Python
多线程的应用,但是一直没有系统学习,加之最近有了相关需求且发现其在Linux
上的执行效率是真的高便决定简单学习并做个记录。
基本用法
import threading
def thread_job():
print('This is an added Thread, number is %s' %threading.current_thread())
def main():
# 输出激活状态的线程数
# 1
print(threading.active_count())
# 输出当前激活状态线程的详细信息
# [<_MainThread(MainThread, started 14940)>]
print(threading.enumerate())
# 输出正在运行的线程是哪一个
# <_MainThread(MainThread, started 14940)>
print(threading.current_thread())
# 新建一个线程
# This is an added Thread, number is <Thread(Thread-1, started 9948)>
added_thread = threading.Thread(target=thread_job)
added_thread.start()
if __name__ == '__main__':
main()
join
方法
默认情况下,多线程任务中子线程的运行并不会阻塞主线程,使用join
方法即可对主线程进行阻塞。
import threading
from time import sleep
def thread_job():
print('T1 start\n')
for i in range(10):
sleep(0.1)
print('T1 finish\n')
def main():
added_thread = threading.Thread(target=thread_job, name="T1")
added_thread.start()
print('all done\n')
if __name__ == '__main__':
main()
输出顺序:
T1 start
all done
T1 finish
阻塞状态:
import threading
from time import sleep
def thread_job():
print('T1 start\n')
for i in range(10):
sleep(0.1)
print('T1 finish\n')
def main():
added_thread = threading.Thread(target=thread_job, name="T1")
added_thread.start()
added_thread.join() # 随后的语句将被阻塞
print('all done\n')
if __name__ == '__main__':
main()
输出顺序:
T1 start
T1 finish
all done
双线程模型:
import threading
from time import sleep
def thread_job():
print('T1 start\n')
for i in range(10):
sleep(0.1)
print('T1 finish\n')
# 一个执行较快的函数
def T2_job():
print('T2 start\n')
print('T2 finish\n')
def main():
added_thread = threading.Thread(target=thread_job, name="T1")
thread_2 = threading.Thread(target=T2_job, name="T2")
added_thread.start()
thread_2.start()
added_thread.join() # 随后的语句将被阻塞
print('all done\n')
if __name__ == '__main__':
main()
输出结果:
T1 start
T2 start
T2 finish
T1 finish
all done
即便是运行较快的线程,只要不阻塞,其优先级也会低于主线程:
import threading
from time import sleep
def thread_job():
print('T1 start\n')
for i in range(10):
sleep(0.1)
print('T1 finish\n')
# 一个执行较快的函数
def T2_job():
print('T2 start\n')
print('T2 finish\n')
def main():
added_thread = threading.Thread(target=thread_job, name="T1")
thread_2 = threading.Thread(target=T2_job, name="T2")
added_thread.start()
added_thread.join() # 随后的语句将被阻塞
thread_2.start()
# thread_2.join() # 随后的语句将被阻塞
print('all done\n')
if __name__ == '__main__':
main()
输出结果:
T1 start
T1 finish
T2 start
all done
T2 finish
Queue
功能
由于多线程没有返回值,我们可以将其运算结果放在一个队列中,随后每个线程的队列到主线程中后即可取出数据。
以计算列表中每个元素值的平方为例:
import threading
from queue import Queue
# 二次方的实现
def job(l,q):
for i in range(len(l)):
l[i] = l[i] ** 2
q.put(l)
# 多线程模块
def multithreading():
# 定义队列(放返回值以取代return)
q = Queue()
# 所有的线程放入本列表
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))
# 启动线程
t.start()
# 加入到所有线程中
threads.append(t)
# 阻塞每一个线程以实现异步(需要并发而不是依次执行)
for thread in threads:
thread.join()
# 定义空列表(用来存每一个线程返回过来的值)
results = []
for _ in range(4):
# 按顺序从q中取出值存到results
results.append(q.get())
print(results)
if __name__ == '__main__':
multithreading()
全局解释器锁GIL
未必提高效率
Python
的多线程,只有用于I/O
密集型程序时效率才会有明显的提高。
原因如下:
Python
代码的执行是由Python
虚拟机进行控制。它在主循环中同时只能有一个控制线程在执行,意思就是Python
解释器中可以运行多个线程,但是在执行的只有一个线程,其他的处于等待状态。
这些线程执行是有全局解释器锁(GIL
)控制,它来保证同时只有一个线程在运行。在多线程运行环境中,Python
虚拟机执行方式如下:
- 设置
GIL
- 切换进线程
- 执行下面操作之一
1.运行指定数量的字节码指令
2.线程主动让出控制权
- 切换出线程(线程处于睡眠状态)
- 解锁
GIL
- 进入1步骤
注意:
Python
运行计算密集型的多线程程序时,更倾向于让线程在整个时间片内始终占据GIL
,而I/O
秘籍型的多线程程序在I/O被调用前会释放GIL
,以允许其他线程在I/O
执行的时候运行。
下面这段示例运行时多线程未必胜得过单线程:
import threading
import time
import copy
from queue import Queue
def job(l, q):
res = sum(l)
q.put(res)
def multithreading(l):
q = Queue()
threads = []
for i in range(4):
t = threading.Thread(target=job, args=(copy.copy(l),q),name='T%i'%i)
t.start()
threads.append(t)
[t.join() for t in threads]
total = 0
for _ in range(4):
total += q.get()
print(total)
def normal(l):
total = sum(l)
print(total)
if __name__ == '__main__':
l = list(range(1000000))
s_t=time.time()
normal(l*4)
print('normal:',time.time()-s_t)
s_t=time.time()
multithreading(l)
print('multithreading:',time.time()-s_t)
Lock
线程锁
第一个线程处理完再开始第二个线程(针对shared memory
场景):
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()
参考资料:
https://zhuanlan.zhihu.com/p/91601448
https://www.cnblogs.com/guyuyun/p/11185832.html
https://www.bilibili.com/video/BV1jW411Y7Wj
https://docs.python.org/zh-cn/3/library/threading.html
版权属于:soarli
本文链接:https://blog.soarli.top/archives/649.html
转载时须注明出处及本声明。