1. 普通多线程
import threading
import time
def doWaiting():
print(f"start waiting: {time.strftime('%H:%M:%S')}")
time.sleep(3)
print(f"stop waiting: {time.strftime('%H:%M:%S')}")
t = threading.Thread(target = doWaiting)
t.start()
time.sleep(1)
print("Start job")
print("End job")
2. 主线程等子线程:join()
import threading
import time
def doWaiting():
print(f"start waiting: {time.strftime('%H:%M:%S')}")
time.sleep(3)
print(f"stop waiting: {time.strftime('%H:%M:%S')}")
t = threading.Thread(target = doWaiting)
t.start()
time.sleep(1)
print("Start job")
t.join()
print("End job")
3. 主线程带走子线程:setDaemon()
import time
import threading
def sub_thread(num):
time.sleep(1)
print(f"thread {num} is running")
print(f"main_thread starting..")
time.sleep(1)
for i in range(3):
t = threading.Thread(target=sub_thread, args=(i+1, ))
t.setDaemon(True)
t.start()
print("main_thread endded!")
4. 共享全局变量
import threading, time
def add_thread():
global count
for _ in range(10000000):
count += 1
print(f"add_thread运算结束之后,count = {count}")
time.sleep(0.5)
print(f"add thread endded")
def sub_thread():
global count
for _ in range(10000000):
count -= 1
print(f"sub_thread运算结束之后,count = {count}")
time.sleep(0.5)
print(f"sub thread endded")
def main_thread():
global count
print(f"main thread starting...")
t1 = threading.Thread(target=add_thread)
t1.start()
t1.join()
t2 = threading.Thread(target=sub_thread)
t2.start()
t2.join()
count += 88
count = 100
start_time = time.time()
main_thread()
end_time = time.time()
print()
print(f"主线程执行完毕之后,count = {count}")
print("main thread finished!")
print(f"耗时:{round(end_time-start_time, 3)} s")
main thread starting...
add_thread运算结束之后,count = 10000100
add thread endded
sub_thread运算结束之后,count = 100
sub thread endded
主线程执行完毕之后,count = 188
main thread finished!
耗时:2.621 s
5. 互斥锁
import threading, time
def add_thread():
global count
lock.acquire()
for _ in range(10000000):
count += 1
print(f"add_thread运算结束之后,count = {count}")
lock.release()
time.sleep(0.5)
print(f"add thread endded")
def sub_thread():
global count
lock.acquire()
for _ in range(10000000):
count -= 1
print(f"sub_thread运算结束之后,count = {count}")
lock.release()
time.sleep(0.5)
print(f"sub thread endded")
def main_thread():
global count
print(f"main thread starting...")
t1 = threading.Thread(target=add_thread)
t1.start()
t2 = threading.Thread(target=sub_thread)
t2.start()
lock.acquire()
count += 88
lock.release()
count = 100
lock = threading.Lock()
start_time = time.time()
main_thread()
end_time = time.time()
print()
print(f"主线程执行完毕之后,count = {count}")
print("main thread finished!")
print(f"耗时:{round(end_time-start_time, 3)} s")
main thread starting...
add_thread运算结束之后,count = 10000100
add thread endded
sub_thread运算结束之后,count = 100
主线程执行完毕之后,count = 188
main thread finished!
耗时:1.495 s
sub thread endded
6. 信号锁
import threading, time
def sub_thread(num, se):
se.acquire()
print(f"sub_thread {num} is running...")
time.sleep(1)
se.release()
semaphore = threading.BoundedSemaphore(5)
def main_thread():
for i in range(20):
t = threading.Thread(target=sub_thread, args=(i+1, semaphore))
t.start()
main_thread()
7. 事件锁
'''
主要提供以下的几个方法:
- clear将flag设置为 False
- set将flag设置为 True
- is_set判断是否设置了flag
- wait会一直监听flag,如果没有检测到flag就一直处于阻塞状态
事件处理的机制:
全局定义了一个Flag,当Flag的值为False,那么event.wait()就会阻塞,
当flag值为True,那么event.wait()便不再阻塞
import threading, time
from threading import Timer
event = threading.Event()
def lighter(waiting_time, lighting):
if not event.is_set():
if waiting_time >= 5:
event.set()
lighting = "Green"
print("Go!")
else:
print("wait...")
event.wait()
else:
if lighting == "Green":
print("Go!")
else:
print("Stop!")
def car_control(waiting_time=0, flag=None):
if flag == True:
event.set()
lighting = "Green"
else:
event.clear()
lighting = "Red"
lighter(waiting_time, lighting)
car_control(10)
8. 全局解释器锁 GIL
'''
在非 python 环境中,单核情况下,同时只能有一个任务执行。多核时可以支持多个线程同时执行。
但是在 python 中,无论有多少个核同时只能执行一个线程。究其原因,这就是由于GIL的存在导致的。
GIL 是 Python 设计之初为了数据安全所做的决定。
Python 中的某个线程想要执行,必须先拿到 GIL。可以把GIL看作是执行任务的“通行证”,并且在一个Python进程中,GIL只有一个。拿不到通行证的线程,就不允许进入CPU执行。
GIL 只在 CPython解释器中才有,因为 CPython调用的是c语言的原生线程,不能直接操作cpu,只能利用GIL保证同一时间只能有一个线程拿到数据。
在 PyPy 和 JPython 中没有GIL。
虽然,在Python的不同解释器实现中,如PyPy就移除了GIL,其执行速度更快(不单单是去除GIL的原因)。
但是,我们通常使用的CPython解释器版本占有着统治地位的使用量
'''
'''
Python多线程的工作流程:
- 拿到公共数据
- 申请GIL
- Python解释器调用操作系统原生线程
- cpu执行运算
- 当该线程执行一段时间消耗完,无论任务是否已经执行完毕,都会释放GIL
- 下一个被CPU调度的线程重复上面的过程
'''
'''
Python针对不同类型的任务,多线程执行效率是不同的:
- 对于CPU密集型任务(各种循环处理、计算等等),由于计算工作多,ticks计数很快就会达到阈值,然后触发GIL的释放与再竞争(多个线程来回切换是需要消耗资源的),所以Python下的多线程对CPU密集型任务并不友好。
- IO密集型任务(文件处理、网络通信等涉及数据读写的操作),多线程能够有效提升效率(单线程下有IO操作会进行IO等待,造成不必要的时间浪费,而开启多线程能在线程A等待时,自动切换到线程B,可以不浪费CPU的资源,从而能提升程序执行效率)。所以Python的多线程对IO密集型任务比较友好。
'''
'''
在实际使用中的建议:
- Python中想要充分利用多核CPU,就用多进程。因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行。
- 在Python中,多进程的执行效率优于多线程(仅仅针对多核CPU而言)。同时建议在IO密集型任务中使用多线程,在计算密集型任务中使用多进程。
- 另外,深入研究Python的协程机制,你会有惊喜的。
'''
多进程方面更多参考:
- python 彻底解读多线程与多进程
- 一文看懂Python多进程与多线程编程(工作学习面试必读)
参考:
- 多线程threading——简单明了
- python多线程详解(超详细)
- ——简易理解为什么 Python 多线程是伪多线程
|