20、多线程编程
一、线程基础
import threading # 新建: 使用线程的第一步就是创建线程, 创建后的线程只是进入可执行的状态, 也就是Runnable # Runnable: 进入此状态的线程还并未开始运行, 一旦CPU分配时间片给这个线程后, 该线程才正式的开始运行 # Running: 线程正式开始运行, 在运行过程中线程可能会进入阻塞的状态, 即Blocked # Blocked: 在该状态下, 线程暂停运行, 解除阻塞后, 线程会进入Runnable状态, 等待CPU再次分配时间片给它 # 结束: 线程方法执行完毕或者因为异常终止返回 # 注意:sys.exit()只能结束当前线程 # 一、threading模块的三大函数: # threading.current_thread()(或者threading.currentThread()), # threading.active_count()(或者threading.activeCount()), # threading.main_thread() print(threading.current_thread()) # 当前线程 <_MainThread(MainThread, started 3788)> print(threading.active_count()) # 1 活动线程总数 print(threading.main_thread()) # 主线程 <_MainThread(MainThread, started 3788)> print(threading.currentThread().name) # MainThread # 二、创建线程 # 方法一 def fun(*a,b): print(threading.currentThread()) # <Thread(tangjun, started 1328)> print(a, b) # 1, 2, 3) 这是关键字参数 t = threading.Thread(target=fun, args=(1, 2, 3), kwargs={'b':'这是关键字参数'}, name='tangjun') # args 和 name可选,默认name是Thread-1,Thread-2...当有些参数必须用关键字传递值是就要用到:kwargs,其值是一个字典 t.start() # 进入可执行的状态, 也就是Runnable,注意,并非马上执行,要看cpu是否给它机会 # 方法二 class MyThread(threading.Thread): # 注意,如果没有额外的参数,请不要重写父类构造方法,子类会自动调用父类的构造方法并传递参数 def run(self) -> None: print(threading.currentThread()) # <MyThread(xiaodng, started 11432)> print(threading.activeCount()) t = MyThread(name='xiaodng') # 参数是线程的名字,如果没有必要的话可以省略 t.start() # 进入可执行的状态, 也就是Runnable,注意,并非马上执行,要看cpu是否给它机会
二、重要方法
1 thread.join(timeout) 让thread线程先执行,我等一等
暂停当前线程,等待thread线程结束(不设参数值,默认None)(或timeout秒后)再运行当前线程
三、重要的类
线程锁:别的线程等一等,我先执行
threading.Lock() threading.RLock() threading.Condition()
1、threading.Lock()
look = threading.Lock() # 创建一个线程锁,注意,所有的线程要共有一把锁才能锁住资源,
.............................................
# 某个线程进入...
look.acquire() # 锁住资源
……运行可能会出现数据安全的代码
look.release() # 释放锁
2、threading.RLock() 在同一个线程内可以重复锁几次都不会锁死
import threading
rLock = threading.RLock() #RLock对象
rLock.acquire()
rLock.acquire() #在同一线程内,程序不会堵塞。
rLock.release() # 释放锁,加几次锁就只能释放几次,不能多释放
rLock.release()
3、threading.Condition 条件,高级锁,能高替代上面那两种锁,除此之个还提供了额外的功能:详情见后面
# 1、con = threading.Condition() 创建锁
# 2、 con.acquire() # 给当前线程加锁,注意,可以反复加锁,一直持有锁。
# 3、con.release() 释放锁,加几次锁就只能释放几次,不能多释放
# 4、只有持有锁的线程(con.acquire())才可以wait(),notify(),notifyAll()
# 5、wait(timeout=None)等待,当前线程挂起(阻塞),同时自动释放锁(con.release()),可以设置超时timeout
# 6、notify(n=1)随机唤醒等待池中的n个线程,通知这n个线程去竟争锁acquire,最终只有一个线程内争到锁。参数默认值是1,可以传递一个自已喜欢的值。notify()可以替代notifyAll()
# 7、notifyAll()唤醒等待池中的所有线程,通知所有线程去竟争锁acquire。虽然全部都唤醒了,但是只有一个线程能够竟争到锁,其它线程在等待释放锁
# coding=utf-8 import threading, time con = threading.Condition() num = 0 # 生产者 class Producer(threading.Thread): def run(self): # 锁定线程 global num con.acquire() # 获得锁 while True: print("开始添加!!!") num += 1 print("火锅里面鱼丸个数:%s" % str(num)) time.sleep(1) if num >= 5: print("火锅里面里面鱼丸数量已经到达5个,无法添加了!") # 唤醒等待的线程 con.notify() # 唤醒小伙伴开吃啦 # 等待通知 con.wait() # 等待的同时会自动释放锁 # 消费者 class Consumers(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): con.acquire() global num while True: print("开始吃啦!!!") num -= 1 print("火锅里面剩余鱼丸数量:%s" %str(num)) time.sleep(2) if num <= 0: print("锅底没货了,赶紧加鱼丸吧!") con.notify() # 唤醒其它1个线程 # 等待通知 con.wait() p = Producer() c = Consumers() p.start() c.start()
# coding=utf-8 # 线程 交替运行两个线程 import threading, time class MyThread(threading.Thread): def run(self) -> None: global cn, count cn.acquire() while count < 10: count += 1 print(threading.current_thread(), count) time.sleep(1) cn.notify() cn.wait() # 程序运行到此处时还有一个线程处于wait状态,所以需要notify,整个程序才会结束 cn.notify() cn.release() cn = threading.Condition() count = 0 if __name__ == "__main__": mt1 = MyThread() mt2 = MyThread() mt1.start() mt2.start()
三、守护线程
当主线程结束后守护线程会立即结束
import threading import time def run(n):
# 守护进程在执行下面的代码的时候,完全凭“运气”,也许能执行一条语句,也许一条也不能执行,因为主线程结束后守护线程就会被立即结束 print("task", n) time.sleep(1) # 此时子线程停1s print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) # 把子进程设置为守护线程,必须在start()之前设置 t.start() # 进入可执行的状态, 也就是Runnable,注意,并非马上执行,要看cpu是否给它机会 print("end")
四,threading.Event
需要注意的是:event.set() 以后,event.wait()就不阻塞,所有的线程都不阻塞,event.clear()以后,将阻塞所有线程的event.wait()方法 。
# Event,与Condition的区别:Event没有锁,不存在竟争锁,多个线程可以同时运行(由cup随机调试),Condition有锁,多个线程要竟争锁,获得锁的线程才可以运行 # 1 event.isSet() 标志,默认值为False,event.wait()会被阻塞,当其值为True时event.wait()不会被阻塞 # 2 event.wait(timeout) # 根据标志(isSet())决定线程是否会被阻塞,默认isSet()为False,wait()是会被阻塞的。 # 3 event.set() 设置标志isSet()的值为True.此时所有wait()被阻塞的线程都会畅通。 # 4 event.clear() 设置标志isSet()的值为False,此时所有wait()的线程会被阻塞 import threading, time def light(): event.set() # 打开绿灯,wait()不会被阻塞 count = 0 n = 0 status = "" while True: if count < 10: status = "绿灯亮 ---" elif count < 13: status = "黄灯亮------" elif count < 20: event.clear() status = "红灯亮---------" else: count = 0 event.set() # 打开绿灯 print(status) time.sleep(1) count += 1 n += 1 if n > 20: break def car(n): t = 0 while True: time.sleep(1) if event.isSet(): # 如果是绿灯 print("car [%s] is 正在跑.." % n) else: print("car [%s] is 在等红灯.." % n)
event.wait()#去检测,标志位没有设置,卡在这 if (t > 20): break else: t += 1 if __name__ == '__main__': event = threading.Event() Light = threading.Thread(target=light) Light.start() for i in range(2): t = threading.Thread(target=car, args=(i,)) t.start()
五,定时器
import threading # Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。Timer从Thread派生,没有增加实例方法 # interval: 指定的时间 # function: 要执行的方法 # args / kwargs: 方法的参数 def func(num): print('hello {} timer!'.format(num)) # 如果t时候启动的函数是含有参数的,直接在后面传入参数元组 timer = threading.Timer(5, func,(1,)) timer.start() # timer.cancel() 取消定时器 print(__name__)
六、local 和 get_ident() 注意 local是一个小写的类
import threading import time # 这是一个特殊的对象 ret = threading.local() # 先实例化一个对象,注意,这是一个小写的类 ret.a = 0 # 这个对象真的很特殊,每个线程单独拥用该对象。互相不会影响 print(threading.get_ident()) # 每个线程都有一个独一无二的ident def task(arg): # 写个任务,加个参数 # global ret 不能设置成全局,否则会丧失它的特殊性 # ret = arg # 每一个线程进来都给他开辟一个独立的空间 单纯的threading.local()的作用就是这个 # 如果上面给ret赋值int类型,是不可以ret.a ret.a = arg # time.sleep(1) print(ret.a, end='') # 每个线程来,改了ret,然后取ret的值 # 8405391267 顺序不一定 for i in range(10): # i是线程的值,0 1 2 3 4 5 6 7 8 9 t = threading.Thread(target=task, args=(i,)) # 开10个线程 t.start() print(threading.get_ident()) # 每个线程都有一个独一无二的ident
七、线程还有哪些用途,心德:
简单说几点:
1、可以用作程序暂停,time.sleep(n)暂停时间是写死了的,wait()暂停到什到时候取决于别的线程何时唤醒它
2、as3中经常使用事件驱动,python就很少使用事件,可以把线程当作事件驱动,比如你wait()以后我在完成一项任务以后notify()你
八、天哪,终于学完了,学会了


浙公网安备 33010602011771号