线程同步

概念

线程同步,线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作。

Event

Event事件,是线程间通信机制中最简单的实现,使用一个内部的标记flag,通过flag的True和False的变化来进行操作。

名称含义
set()标记设置为True
clear()标记设置为False
is_set()标记是否为True
wait(timeout=None)设置等待标记为True的时长,None为无限等待,等到返回True,未等到超时返回False
from threading import Event,Thread
import threading
import time
import logging
FORMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)

def boss(event : Event):
    logging.info('boss wait')
    event.wait()
    logging.info('finish~~~~~~~~~~~~~BOSS')
def work(event:Event,count=10):
    logging.info('work')
    cups=[]
    while True:
        logging.info('make cup')
        time.sleep(0.5)
        cups.append(1)
        if len(cups)>=count:
            event.set()
            break
    logging.info('finish,cup={}'.format(len(cups)))
event=Event()
b=threading.Thread(target=boss,name='boss',args=(event,))
w=threading.Thread(target=work,name='work',args=(event,))
b.start()
w.start()

在这里插入图片描述
总结:
使用同一个Event对象的标记flag
谁wait就是等待flag变为True,或等到超时返回False,不限制使用个数。

  • wait使用
import time
import logging
FORMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
def work(event:Event,interval:int):
    while not event.wait(interval):
        logging.info('do')
event=Event()
threading.Thread(target=work,args=(event,3)).start()
event.wait(10)
print('~~~~~~~~~~~~~~~~~~``````')
event.set()

在这里插入图片描述

定时器 Timer/延时执行

threading.Timer继承自Thread,这个类用来定义延迟多久后执行一个函数。

class threading.Timer(interval,function,args=None,kwargs=None)

start方法执行之后,Timer对象会处于等待状态,等待了interval秒之后,开始执行function函数的

import threading
import logging
import time
FARMT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FARMT,level=logging.INFO)

def work():
    logging.info('do')
    time.sleep(1)
t=threading.Timer(4,work)
# 改变线程的名字
t.setName('timmer')
# t.cancel()
t.start()
#t.cancel()
while True:
    print(threading.enumerate())
    time.sleep(1)

在这里插入图片描述
上例代码工作线程早就启动了,只不过是在工作线程中延时4秒才执行了work函数。
Time是线程Thread的子类,Timer实例内部提供了一个finish属性,该属性Event对象,cancel方法,本质上是在work函数执行前对finished属性set方法操作,从而跳过了work函数执行,达到取消的效果。
总结:
Timer是线程Thread的子类,就是线程类,具有线程的能力和特征。
它的实例是能够延时执行目标函数的线程,在真正执行目标函数之前,都可以cancel它。
cancel方法本质上使用了Event类实现,并不是说线程提供了取消的方法。

Lock

锁:一旦线程获得锁,其它试图获取锁的线程将被阻塞。
锁:凡是存在共享资源争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源。

名称含义
acquire(blocking=True,timeout=-1)默认阻塞,阻塞可以设置超时时间。非阻塞时,timeout禁止设置,成功获取锁,返回True,否则返回False
release()释放锁,可以从任何线程调用释放,已上的锁,会被重置为unlocked未上锁的锁上调用,抛RuntimeError异常
import threading
from threading import Lock
lock=threading.Lock()
lock.acquire()
print('~~~等待~~~')
def work(lock : Lock):
    print(threading.current_thread().name)
    lock.acquire()
    print(threading.current_thread().name)
for i in range(10):
    threading.Thread(target=work,args=(lock,),name='{}'.format(i+1)).start()
print('~~~~~~~~~~~~~~~~')

在这里插入图片描述
上例可以看出一个线程上了锁之后,其他线程对这个锁请求,该线程会阻塞,其他线程会等待。
订单要求生产1000个杯子,组织10个工人生产。

from threading import Thread,Lock
import  logging
import time
FORMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
cups=[]
def work(count=10):
    logging.info('work')
    while len(cups)<count:
        time.sleep(0.01)
        cups.append(1)
    logging.info("{}".format(len(cups)))
for i in range(1,11):
    t=Thread(target=work,name='{}'.format(i),args=(1000,))
    t.start()

在这里插入图片描述
上例运行结果来看,多线程调度,导致了判断失效,多生产了杯子。

#增加锁服务
from threading import Thread,Lock
import  logging
import time
FORMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
cups=[]
lock=Lock()
def work(count=10):
    logging.info('work')
    flag=False
    while True:
        lock.acquire()
        if len(cups)>=count:
            flag=True
        time.sleep(0.01)
        if not flag:
            cups.append(1)
            lock.release()
        if flag:
            break
    logging.info('{}'.format(len(cups)))

for i in range(1,11):
    t=Thread(target=work,args=(1000,),name='{}'.format(i))
    t.start()

在这里插入图片描述
计算器类,可以加、可以减

import threading
from threading import Thread,Lock
import time
class Counter:
    def __init__(self):
        self._value=0
    @property
    def value(self):
        return self._value
    def inc(self):
        self._value+=1
    def dec(self):
        self._value-=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=10
for i in range(c1):
    Thread(target=run,args=(c,c2)).start()
print(c.value)
  • c1为线程
    c2为执行次数
    当c1和c2增大后有可能会影响到最后的数值,因为在执行过程中可能会有同一时刻操作同一变量的时候。
加锁、解锁

一般来说,加锁就需要解锁,而且是谁加了锁,就需要本身自己解锁,但是家加锁后解锁前,还有一些代码执行,就有可能抛异常,一旦出现异常,锁无法释放,但是当前线程可能因为异常被终结,有可能产生死锁。
加锁、解锁常用语句:
1、使用try……finally语句保证锁的释放
2、with上下文管理,锁对象支持上下文管理

import threading
from threading import Thread,Lock
import time
class Counter:
    def __init__(self):
        self._value=0
        self.__lock=Lock()
    @property
    def value(self):
        with self.__lock:
            return self._value
    def inc(self):
        try:
            self.__lock.acquire()
            self._value+=1
        finally:
            self.__lock.release()
    def dec(self):
        with self.__lock:
            self._value-=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=100
for i in range(c1):
    Thread(target=run,args=(c,c2)).start()
#因为是线程同步,所以有可能上面的线程未执行完,主线程终止,所取到的值可能不是最后的值
while True:
    time.sleep(1)
    if threading.active_count()==1:
        print(c.value)
        break
    else:
        print(threading.enumerate())
锁应用场景

锁适合用于访问和修改同一个共享资源的时候,即读写同一个资源的时候。
使用锁注意事项:

  • 少用锁,必要时用锁,使用锁,多线程访问被锁的资源时,会形成串行,要么排队执行,要么争抢执行。
  • 加锁时间越短越好,不需要立刻释放锁
  • 一定要避免死锁

不使用锁,有了效率,可能结果是错的
使用了锁,效率低下,结果是对的。

非阻塞锁
import threading
import logging
import time
FORMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
lock=threading.Lock()
def work(lock : threading.Lock):
    while True:
        flag=lock.acquire(False)
        if flag:
            logging.info('work')
        else:
            logging.info('no---{}'.format(threading.current_thread().name))
            time.sleep(1)
            break
for i in range(5):
    threading.Thread(target=work,name='{}'.format(i+1),args=(lock,)).start()

在这里插入图片描述

import threading
#其中的False代表得不到锁不等待
lock=threading.Thread.Lock(False)
可重入锁

可重入锁,是线程相关的锁。
线程A获得可重复锁,并可以多次成功获取,不会阻塞。最后要在线程A中做和acquire次数相同的release。

import logging
import threading
FARMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FARMAT,level=logging.INFO)
lock=threading.RLock()
#主线程获取锁
# print(lock.acquire())
def foo(lock:threading.Lock):
    print(threading.current_thread(),lock.acquire())
    print(threading.current_thread(), lock.acquire())
    print(lock)
    lock.release()
    print('release1')
    lock.release()
    print('2')
print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~``')
threading.Timer(2,foo,(lock,)).start()

在这里插入图片描述
可重入锁

  • 与线程相关,可在一个线程中获取锁,并可继续在同一线程中不阻塞多次获取锁
  • 当锁未释放完,其他线程获取锁就会阻塞,直到当前持有锁释放完
  • 锁都应该使用完后释放,可重入锁也是锁,应该acquire多少次,就release多少次
condition

构造方法Condition(lock=None),可以传入一个Lock或RLock对象,默认是RLock.

名称含义
acquire(%args)获取锁
wait(self,timeout=None)等待或超时
notify(n=1)唤醒至多指定数目的等待线程,没有等待的线程就没任何操作
notify_all()唤醒所有等待的线程
import logging
import threading
import time
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format="",level=logging.INFO)
cond=threading.Condition()
def boss():
     logging.info('wait')
     with cond:
         cond.wait()
     logging.info('ok')
def work(count=1000):
    logging.info('work')
    with cond:
        cups=[]
        while len(cups)<count:
            time.sleep(0.001)
            cups.append(1)
        print(threading.current_thread().name,len(cups))
        cond.notify_all()
b1=threading.Thread(target=boss,name='boss1')
b2=threading.Thread(target=boss,name='boss2')
b1.start()
b2.start()
w=threading.Thread(target=work,name='work')
w.start()

在这里插入图片描述
Condition用于生产者、消费者模型,为了解决生产者消费者速度匹配问题。

from threading import Event,Thread,Condition
import logging
import random
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class Dispachter:
    def __init__(self):
        self.data=None
        self.event=Event()
    def produce(self,total):
        for _ in range(total):
            data=random.randint(1,100)
            logging.info("{}{}".format(data,'produce'))
            self.data=data
            self.event.wait(1)
    def consume(self):
        while not self.event.is_set():
            data=self.data
            logging.info("{}{}".format(data,'consume'))
            self.data=None
            self.event.wait(0.5)
d=Dispachter()
p=Thread(target=d.produce,name='producer',args=(10,))
c=Thread(target=d.consume,name='consumer')
c.start()
p.start()

在这里插入图片描述
这个例子采用了消费者主动消费,消费者浪费了大量时间,主动来查看有没有数据。

from threading import Event,Thread,Condition
import logging
import random
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class Dispachter:
    def __init__(self):
        self.data=None
        self.con=Condition()
    def produce(self,total):
        for _ in range(total):
            with self.con:
                data=random.randint(1,100)
                logging.info("{}{}".format(data,'produce'))
                self.data=data
                self.con.wait(1)
                self.con.notify(1)
    def consume(self):
        while True:
            with self.con:
                data=self.data
                logging.info("{}{}".format(data,'consume'))
                self.data=None
                self.con.wait()
d=Dispachter()
p=Thread(target=d.produce,name='producer',args=(10,))
c=Thread(target=d.consume,name='consumer')
c.start()
p.start()

在这里插入图片描述
上例中,消费者等待数据,如果生产者准备好了会通知消费者消费,节省了消费者反复来查看数据是否就绪。

from threading import Event,Thread,Condition
import logging
import random
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class Dispachter:
    def __init__(self):
        self.data=None
        self.con=Condition()
        self.event=Event()
    def produce(self,total):
        for _ in range(total):
            with self.con:
                data=random.randint(1,100)
                logging.info("{}{}".format(data,'produce'))
                self.data=data
                self.con.notify(1)
            self.event.wait(1)
    def consume(self):
        while True:
            with self.con:
                self.con.wait()
                data=self.data
                logging.info("{}{}".format(data,'consume'))
d=Dispachter()
p = Thread(target=d.produce, name='producer', args=(10,))
p.start()
for i in range(5):
    c = Thread(target=d.consume, name='consumer')
    c.start()

在这里插入图片描述
上例实现了广播模式。
condition总结

  • Condition用于生产者消费者模型中,解决生产者消费者速度匹配的问题
  • 采用了通知机制。

使用方式:

  • 使用Condition,必须先acquire,用完了要release,因为内部使用了锁,默认使用了RLock锁,最好的方式是使用with上下文
semaptore 信号量

和Lock很像,信号量对象内部维护了一个倒计数器,每次acquire都会减1,当acquire方法发现技术为0就阻塞请求的线程,直到其它线程对信号量release后,计数大于0,恢复阻塞的线程。

名称含义
Semaphore(value=1)构造方法。value小于0,抛ValueError异常
acquire(blocking=True,timeout=None)获取信号量,计数器减1,获取成功返回True
release()释放信号量,计数器加1

计数器永远不会低于0,因为acquire的时候,发现是0,都会被阻塞。

from threading import  Thread,Semaphore
import logging
import  time
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)
def work(s:Semaphore):
    logging.info("work")
    logging.info(s.acquire())
    logging.info("over")
#信号量
s=Semaphore(3)
logging.info(s.acquire())
print(s._value)
logging.info(s.acquire())
print(s._value)
logging.info(s.acquire())
print(s._value)
Thread(target=work,args=(s,)).start()
time.sleep(2)
logging.info(s.acquire(False))
logging.info(s.acquire(timeout=3))
#释放一个
logging.info('release one')
s.release()

在这里插入图片描述
Semaphore规定acquice不可以超出规定数量,但是没有规定release不能超过。
release方法超界

import logging
import threading
se=threading.Semaphore(3)
logging.warning(1)
logging.warning(se.__dict__)
for i in range(3):
    se.acquire()
logging.warning(2)
logging.warning(se.__dict__)
for i in range(4):
    se.release()
logging.warning(3)
logging.warning(se.__dict__)

for i in range(3):
    se.acquire()
logging.warning(4)
logging.warning(se.__dict__)
se.acquire()
logging.warning(5)
logging.warning(se.__dict__)

在这里插入图片描述
从上例输出结果可以看出,竟然内置计数器达到了4,这样实际超出了我们的最大值。

BoundedSemaphore类

有界的信号量,不允许使用release超出初始值的范围,否则,抛出ValueError异常
连接池:因为资源有限,且开启一个连接成本高,所以,使用连接池。
一个简单的连接池:连接池应该有总数,有一个工厂方法可以获取连接,能够把不用的连接返回,供其他调用者使用。

import threading
import logging
import time
import random
FORMAT='%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)
class C:
    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(self.count))]
        self.semaphore=threading.Semaphore(count)
    def _connect(self,conn_name):
        return C(conn_name)
    def get_conn(self):
        logging.info('get')
        self.semaphore.acquire()
        return self.pool.pop()
    def return_conn(self,con:C):
        logging.info('return')
        self.pool.append(con)
        self.semaphore.release()
pool=Pool(3)
def work(pool:Pool):
    con=pool.get_conn()
    logging.info(con)
    time.sleep(random.randint(1,5))
    pool.return_conn(con)
for i in range(5):
    threading.Thread(target=work,name='work-{}'.format(i),args=(pool,)).start()

在这里插入图片描述
上例中,使用信号量解决资源有限的问题。
如果池中有资源,请求者获取资源时信号量减1,拿走资源,当请求超过资源数,请求者只能等待,当使用者用完归还资源后信号量加1,等待线程就可以被唤醒拿走资源。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值