Python 多线程:伪多线程?详细代码总结

1. 普通多线程

# 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()

# 2. 主线程等待子线程(本质上将多线程变成了单线程)

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()

# 3. 主线程结束,则全部子线程被强制终止(子线程为守护线程)

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)   # 把子线程设置为守护线程,必须在start()之前设置
    t.start()
#     t.join()  # 可以使用 join()让主程序等子线程
print("main_thread endded!")

4. 共享全局变量

# 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()     # join是阻塞等线程执行完毕。
    
    t2 = threading.Thread(target=sub_thread)
    t2.start()
    t2.join()     # 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")


# 最终:主线程执行完毕之后,count 的结果是不固定的

# 注:如果使用 t.join() ,则将线程变为顺序执行(本质上多线程变成了单线程)
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. 互斥锁

# 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")

# 最终:主线程执行完毕之后,count 的结果是188

# 注:可以使用 t.join() 来设置主线程等待子线程结束
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. 信号锁

# 6. 信号锁 Semaphore:许一定数量的线程同时更改数据

import  threading, time

def sub_thread(num, se):
    se.acquire()
    print(f"sub_thread {num} is running...")
    time.sleep(1)
    se.release()
    
# 设置允许 5 个线程同时运行
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. 事件锁

# 7. 事件线程锁 event:用于主线程控制其他线程的执行,事件是一个简单的线程同步对象
'''
主要提供以下的几个方法:
- 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:   # 即使开始的状态是 没有定义 flag,但是等待时间超过 5 s则可通过
            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(3)          # wait...
car_control(10)         # Go!
# car_control(0, True)    # Go!
# car_control()             # wait...

8. 全局解释器锁 GIL

# 8. GIL 全局解释器锁(Global Interpreter Lock)

'''
在非 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的协程机制,你会有惊喜的。
'''

多进程方面更多参考:

  1. python 彻底解读多线程与多进程
  2. 一文看懂Python多进程与多线程编程(工作学习面试必读)

参考:

  1. 多线程threading——简单明了
  2. python多线程详解(超详细)
  3. ——简易理解为什么 Python 多线程是伪多线程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值