概念
* 线程同步,线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完 成对数据的操作。
名称 | 含义 |
---|---|
event.set() | 标记设置为True |
event.clear() | 标记设置为False |
event.is_set() | 标记是否为True |
event.wait(timeout=None) | 设置等待标记为True的时长,None为无限等待。等到返回True,未等到超时了返回False |
import threading
import time
import logging
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
def worker(event:threading.Event,count = 10):
logging.info("我是worker工作线程")
cups = []
while True:
logging.info("制作了一个 cup")
time.sleep(0.2)
cups.append(1)
if len(cups)>=count:
event.set()
break
logging.info("制作完成:{}".format(cups))
def boss(event:threading.Event):
logging.info("我是boss")
event.wait()
logging.info("Good Job")
event = threading.Event()
b = threading.Thread(target=boss,args=(event,))
w = threading.Thread(target=worker,args=(event,))
b.start()
w.start()
使用同一个Event对象的标记flag。
谁wait就是等到flag变为True,或等到超时返回False。不限制等待的个数。
wait的使用
from threading import Thread,Event
import logging
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
def worker(event:Event,interval:int):
while not event.wait(interval):
logging.info("没有等到。。")
e = Event()
Thread(target=worker,args=(e,1)).start()
e.wait(5)
e.set()
print("======end========")
方法 | 含义 |
---|---|
Timer.cancel() | 取消定时器,(定时器为执行函数时可以取消,在函数执行中无法取消) |
Time.start() | 启动定时器 |
class threading.Timer(interval, function, args=None, kwargs=None)
from threading import Timer
import logging
import time
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
def worker():
logging.info("in worker")
time.sleep(5)
logging.info("end in worker")
t = Timer(2,worker)
t.setName("timer1") #设置线程名称
# t.cancel() #取消定时器后,定时器不在执行
t.start()
# t.cancel() #取消定时器后,定时器不在执行
time.sleep(4) #等待4秒后,定时器已经开始执行
t.cancel() #当定时器执行后,无法取消
print("======end========")
锁(Lock):一旦线程获得锁,其他试图获取锁的线程将被阻塞等待。
锁:凡是存在共享支援争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源。
名称 | 含义 |
---|---|
Lock.acquire(blocking=True,timeout=-1) | 获取锁,获取成功返回True,否则返回False 当获取不到锁时,默认进入阻塞状态,直到获取到锁,后才继续。阻塞可以设置超时时间。非阻塞时,timeout禁止设置。如果超时依旧未获取到锁,返回False。 |
Lock.rease() | 释放锁,可以从任何线程调用释放。 已上锁的锁,会被设置为unlocked。如果未上锁调用,会抛出RuntimeError异常。 |
import threading
import sys
import time
def print(*args):
sys.stdout.write(" ".join(map(str,args))+"\n")
def worker(lock):
print("worker start",threading.get_ident(),threading.current_thread().name)
lock.acquire()
print("worker over",threading.get_ident(),threading.current_thread().name)
lock = threading.Lock()
lock.acquire()
print(" -"*30)
for i in range(5):
threading.Thread(target=worker,args=(lock,),name="w{}".format(i)).start()
print("- "* 30)
while True:
time.sleep(0.1)
cmd = input(">>").strip()
if cmd == "r":
lock.release()
elif cmd == "q":
break
else:
print(threading.enumerate())
上例可以看出不管在哪一个线程中,只要对一个已经上锁的锁阻塞请求,该线程就会阻塞。
加锁,解锁
一般来说,加锁就需要解锁,但是加锁后解锁前,还要有一些代码执行,就有可能抛异常,一旦出现异常,锁是无 法释放,但是当前线程可能因为这个异常被终止了,这也产生了死锁
加锁、解锁常用语句:
计数器类,可加,可减。
import threading
import sys
import time
def print(*args):
sys.stdout.write(" ".join(map(str,args))+"\n")
class Counter:
def __init__(self):
self._val = 0
self.lock = threading.Lock()
@property
def value(self):
with self.lock:
return self._val
def inc(self):
with self.lock:
self._val += 1
def dec(self):
with self.lock:
self._val -= 1
def run(c:Counter,count=100):
for _ in range(count):
for i in range(-50,50):
if i <0:
c.dec()
else:
c.inc()
c = Counter()
c1 = 10 #线程数
c2 = 1000
for i in range(c1):
threading.Thread(target=run,args=(c,c2)).start()
for k in threading.enumerate():
if k.name != "MainThread":
k.join()
print(c.value)
锁的应用场景
锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候。
使用锁的注意事项:
非阻塞锁的使用
import threading
import sys
import time
def print(*args):
sys.stdout.write(" ".join(map(str,args))+"\n")
def worker(lock:threading.Lock):
while True:
if lock.acquire(False):
print("do something.")
time.sleep(1)
lock.release()
break
else:
print("try again")
time.sleep(1)
lock = threading.Lock()
for i in range(5):
threading.Thread(target=worker,name="w{}".format(i),args=(lock,)).start()
import threading
import sys
import time
def print(*args):
sys.stdout.write(" ".join(map(str,args))+"\n")
def fib(num,rlock:threading.RLock):
with rlock:
if num<3:
return 1
return fib(num-1,rlock)+fib(num-2,rlock)
def work(num,rlock):
print(fib(num,rlock))
rlock = threading.RLock()
for i in range(1,10):
threading.Thread(target=work,args=(i,rlock)).start()
可重入锁
* 与线程相关,可在一个线程中获取锁,并可继续在同一线程中不阻塞多次获取锁
* 当锁未释放完,其它线程获取锁就会阻塞,直到当前持有锁的线程释放完锁
* 锁都应该使用完后释放。可重入锁也是锁,应该acquire多少次,就release多少次
构造方法Condition(lock=None),可以传入一个Lock或RLock对象,默认是RLock。
名称 | 含义 |
---|---|
Condition.acquire(self,*args) | 获取锁 |
Condition.wait(self,timeout=None) | 等待通知,timeout设置超时时间 |
Condition.notify(self,n=1) | 唤醒至多指定数目个数的等待的线程,没有等待的线程就没有任何操作 |
Condition.notify_all(self) | 唤醒所有等待的线程 |
每个线程都可以通过Condition获取已把属于自己的锁,在锁中可以等待其他进程的同级锁的通知。当获取到同级锁的通知后,会停止等待。
当使用Condition(lock=Lock())初始化锁时,锁只能一级等待,不能出现多级等待。
简单示例:
import threading
import time
def work(cond:threading.Condition):
with cond:
print("开始等待")
cond.wait()
print("等到了")
cond = threading.Condition()
# cond = threading.Condition(threading.Lock())
threading.Thread(target=work,args=(cond,)).start()
threading.Thread(target=work,args=(cond,)).start()
with cond:
with cond:
time.sleep(1)
print("开始释放二级等待")
print(cond.notifyAll())
time.sleep(2)
print("开始释放一级等待")
cond.notifyAll()
from threading import Thread,Condition,Lock
import time
import logging
import random
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
class Dispachter:
def __init__(self):
self.data = None
self.cond = Condition(lock=Lock())
#生成者
def produce(self,total):
for _ in range(total):
data = random.randint(1,100)
with self.cond:
logging.info("生产了一个数据:{}".format(data))
self.data = data
self.cond.notify(1)
time.sleep(1) #模拟生成数据需要耗时1秒
#消费者
def consume(self):
while True:
with self.cond:
self.cond.wait() #等待
data = self.data
logging.info("消费了一个数据 {}".format(data))
self.data = None
d = Dispachter()
p = Thread(target=d.produce,name="producer",args=(10,))
# 增加消费者
for i in range(5):
c = Thread(target=d.consume,name="consumer{}".format(i))
c.start()
p.start()
上面例子中演示了生产者生产一个数据,就通知一个消费者消费。
和Lock很像,信号量对象内部维护一个倒计数器,每一次acquire都会减1,当acquire方法发现计数为0就阻塞请求 的线程,直到其它线程对信号量release后,计数大于0,恢复阻塞的线程。
名称 | 含义 |
---|---|
Semaphore(value=1) | 构造方法。value为初始信号量。value小于0,抛出ValueError异常 |
Semaphore.acquire(self,blocking=True,timeout=None) | 获取信号量,技术器减1,即_value的值减少1。如果_value的值为0会变成阻塞状态。获取成功返回True |
Semaphore.release(self) | 释放信号量,计数器加1。即_value的值加1 |
Semaphore._value | 信号量,当前信号量 |
from threading import Semaphore
s =Semaphore(3)
print(s._value)
s.release() #会增加信号量
print(s._value) #可以看出没有做信号量上线控制
print("----------------")
print(s.acquire())
print(s._value)
print(s.acquire())
print(s._value)
print(s.acquire())
print(s._value)
print(s.acquire())
print(s._value)
print(s.acquire()) #当信号量为0再次acquire会被阻塞
print("~~~~~~阻塞了吗?")
print(s._value)
from threading import Thread,Semaphore
import time
import logging
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
#定义获取信号量
def worker(s:Semaphore):
while s.acquire():
logging.info("被执行了一次,获取一个信号量 _value={}".format(s._value))
#释放信号量
def cunn(s:Semaphore):
while True:
logging.info("释放一个信号量")
s.release()
time.sleep(1)
s = Semaphore(3)
#创建3个线程获取信号量
for i in range(3):
Thread(target=worker,args=(s,),name="w{}".format(i)).start()
#开启一个线程释放信号量
Thread(target=cunn,args=(s,)).start()
名称 | 含义 |
---|---|
BoundedSemaphore(value=1) | 构造方法。value为初始信号量。value小于0,抛出ValueError异常 |
BoundedSemaphore.acquire(self,blocking=True,timeout=None) | 获取信号量,技术器减1,即_value的值减少1。如果_value的值为0会变成阻塞状态。获取成功返回True |
BoundedSemaphore.release(self) | 释放信号量,计数器加1。即_value的值加1,超过初始化值会抛出异常ValueError。 |
BoundedSemaphore._value | 信号量,当前信号量 |
from threading import BoundedSemaphore
bs = BoundedSemaphore(3)
print(bs._value)
bs.acquire()
bs.acquire()
bs.acquire()
print(bs._value)
bs.release()
bs.release()
bs.release()
print(bs._value)
bs.release()
from threading import Thread,BoundedSemaphore
import time
import logging
import random
logging.basicConfig(format="%(asctime)s %(threadName)s %(thread)s %(message)s",level=logging.INFO)
#链接类
class Conn:
def __init__(self,name):
self.name = name
class Pool:
def __init__(self,count:int):
self.count = count
#池中放着链接备用
self.pool = [self._connect("conn-{}".format(i)) for i in range(count)]
self.bsemaphore = BoundedSemaphore(count)
#创建连接方法,返回一个连接对象
def _connect(self,conn_name):
return Conn(conn_name)
#获取一个链接
def get_conn(self):
self.bsemaphore.acquire()
self.pool.pop()
logging.info("从连接池拿走了一个连接~~~~~~~")
#归还一个连接
def return_conn(self,conn:Conn):
logging.info("归还了一个连接----------")
self.pool.append(conn)
self.bsemaphore.release()
def worker(pool:Pool):
conn = pool.get_conn()
logging.info(conn)
#模拟使用了一段时间
time.sleep(random.randint(1,5))
pool.return_conn(conn)
pool = Pool(3)
for i in range(6):
Thread(target=worker,name="worker-{}".format(i),args=(pool,)).start()
上例中,使用信号量解决资源有限的问题。
如果池中有资源,请求者获取资源时信号量减1,拿走资源。当请求超过资源数,请求者只能等待。当使用者用完 归还资源后信号量加1,等待线程就可以被唤醒拿走资源。
注意:这个连接池的例子不能用到生成环境,只是为了说明信号量使用的例子,连接池还有很多未完成功能。
threading模块中的类 |
||
类 | 常用方法 | 含义 |
Event | set(self) | 将标记设置为True |
clear(self) | 将标记设置为False | |
is_set() | 判断当前标记是否为True,是True返回True,否则返回False 相当于为返回当前标记 |
|
wait(self,timeout=None) | 如果当前标记为True,立即返回True,如果当前标记为False,会产生一个阻塞,直到标记为True时返回True。timeout等待超时时间,默认为None表示无限等待。未等到超时了返回False | |
Time定时器,延迟执行 | Timer(interval, function, args=None, kwargs=None) | 实例化构造方法 1. interval #多少时间后执行function函数 2. function #需要执行的函数 |
cancel(self) | 取消定时器 (定时器为执行函数时可以取消,在函数执行中无法取消) |
|
start() | 启动定时器 | |
Lock锁 | acquire(self,blocking=True,timeout=-1) | 获取锁,获取成功返回True,否则返回False 当获取不到锁时,默认进入阻塞状态,直到获取到锁,后才继续。 阻塞可以设置超时时间。 非阻塞时,timeout禁止设置。如果超时依旧未获取到锁,返回False。 |
rease(self) | 释放锁,可以从任何线程调用释放。 已上锁的锁,会被设置为unlocked。如果未上锁调用,会抛出RuntimeError异常。 |
|
RLock可重入锁 | 和Lock类似但是: 线程A获得可重复锁,并可以多次成功获取,不会阻塞。最后要在线程A中做和acquire次数相同的release |
|
Condition | Condition(self,lock=None) | 构造方法,lock默认值为None表示使用RLock()锁,也可以自己传入为Lock() |
acquire(self,*args) | 获取锁 | |
rease(self) | 释放锁 | |
wait(self,timeout=None) | 等待通知,timeout设置超时时间。 注意:必须获取锁后才能等待通知,notify或notify_all可以发通知 |
|
notify(self,n=1) | 唤醒至多指定数目个数的等待的线程,没有等待的线程就没有任何操作 | |
notify_all(self) | 唤醒所有等待的线程 | |
Semaphore信号量 | Semaphore(value=1) | 构造方法。value为初始信号量。value小于0,抛出ValueError异常 |
acquire(self,blocking=True,timeout=None) | 获取信号量,技术器减1,即_value的值减少1。 如果_value的值为0会变成阻塞状态。获取成功返回True |
|
release(self) | 释放信号量,计数器加1。即_value的值加1 | |
`_value`属性 | 信号量,当前信号量 | |
BoundedSemaphore有界信号量 | BoundedSemaphore(value=1) | 构造方法。value为初始信号量。value小于0,抛出ValueError异常 |
acquire(self,blocking=True,timeout=None) | 获取信号量,技术器减1,即_value的值减少1。 如果_value的值为0会变成阻塞状态。获取成功返回True |
|
release(self) | 释放信号量,计数器加1。即_value的值加1,超过初始化值会抛出异常ValueError。 | |
_value | 信号量,当前信号量 |