12多线程 并发 并行 threading daemon join Timer Lock RLock

并发与并行

对比解释现实事例类比
并行 parallel同一时刻互不干扰做几件事多车道内同时行车,互不干扰
并发 concurrency强调某一时段处理事情某一时刻某一路段通过大量车辆
高并发的解决方法类比点餐涉及的知识点
队列顾客排队队列是一种天然的解决高并发的方法先进先出的队列(queue),实际上也是一个缓冲区(buffer)
争抢顾客争抢当前争抢人占据窗口,窗口无法为其他人提供服务.是一种锁机制
预处理商家提前准备热门食物预处理/提前加载
并行商家提供多个服务窗口购买更多服务器或多开进程.是一种水平扩展思想
提速提升餐厅服务人员的工作水平提高单个CPU性能或单个服务器安装更多的CPU 是一种垂直扩展的思想
消息中间件地铁内九曲回肠的走廊,缓冲人流常见消息中间件:RabbitMQ ActiveMQ RocketMQ Kafka

进程与线程

类别进程线程
操作系统操作系统进行资源分配和调度的基本单位的基础操作系统能够调度的最小单位
程序执行程序被操作系统加载入内存就是进程,也是线程的容器包含于进程之中,是进程中的实际运作单位
创建速度比创建进程快10~100倍
资源共享进程间不可随便共享数据同一进程内的线程间可共享进程的资源,且每个线程有自己独立的堆栈

- Python中的进程与线程
- Python解释器为操作系统中的一个进程
- 线程共享一个解释器进程
- Python线程开发使用标准库threading
- 创建线程对象用库threading中的Thread类
- init函数签名:threading.Thread.init(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)

参数名含义
target线程调用的对象,即目标函数
name线程名称
args为目标函数传递实参,元组
kwargs为目标函数关键字传参,字典

- 启动线程的代码:

import threading

def worker():
    print("I'm working")

t=threading.Thread(target=worker,name='Worker')#创建线程对象
t.start()#启动线程
  • 线程退出
  • Python线程没有优先级与线程组的概念,不能被销毁 停止 挂起 ,也没有恢复 中断
  • Python没有提供线程退出的方法,线程在下面情况下退出:
  • 线程函数内语句执行完毕
  • 线程函数抛出未处理的异常

  • threading的属性和方法 :

名称含义
current_thread()返当前线程对象
main_thread()返回主线程对象
active_count()当前处于alve状态的线程个数,包括主线程
enumerate()返回所有活着的线程列表 包括主线程,不包括已经终止的线程和未开始的线程
get_ident()返回当前线程的ID,非0整数

- 代码实例 :

import threading
import time

def showthreadinfo():
    print('Current thread :',threading.current_thread())
    print('Main thread :',threading.main_thread())
    print('Active count :',threading.active_count())

def worker():
    time.sleep(2)
    count=0
    showthreadinfo()#若开线程调用worker,则在对应线程内运行
    while True:
        if count > 5:
            break
        time.sleep(1)
        count+=1
        print(count,'I am working',threading.current_thread().ident)
        print(threading.current_thread().name,'\n ')

t=threading.Thread(target=worker,name='Worker')#创建线程对象
showthreadinfo()#主线程运行
t.start()#启动线程 
  • Thread实例的属性与方法
名称含义
nameThread名称 可调用getName() setName()获取或修改
ident线程ID,非0整数 线程启动后才有ID,否则为None
is_alive()返回线程是否活着
start()启动线程(只能执行一次),并在新线程里调用run()
run()运行线程函数,直接运行run()则目标函数在主线程运行

多线程

  • 进程内有多个活动的线程并行工作
  • 一个进程至少有一个线程,该线程为程序的入口,又称为主线程
  • 一个进程至少有一个主线程,其他线程称为工作线程

线程安全

  • 多线程中调用print()函数,会出现不同线程间的print内容在同一行的情况
  • print函数分两步:第一部打印字符串,第二部换行
  • 出现在同一行是因为在打印字符串与换行之间进行了线程的切换
  • 以上情况可称为线程不安全
  • 解决办法 :
  • print(‘print test\n’,end=”)
  • 使用线程安全的logging模块
  • logging是python标准库里的日志处理模块,是线程安全的
  • logging.warning(‘logging test’)

daemon线程

  • 父子线程 : 线程A启动线程B,则称A是B的父线程,B是A的子线程
  • 构造线程时可设置daemon属性,该属性必须在执行start方法前设置好
daemon取值解释
None默认值,此时会取父线程的daemon值
True主线程与子线程并行处理,主线程结束后,若子线程daemon值为true,则不管子线程是否执行完毕,直接退出(为方便理解,只需记得关键字:不等待)
False主线程与子线程并行处理,主线程结束后,若子线程daemon值为False,则等待子线程执行结束再退出

- 主线程daemon值为False,是non-daemon线程

join方法

  • t.start()后执行t.join([timeout=None])
  • timeout默认值为None,此时父线程等待t线程执行结束后再向下执行
  • 若设置timeout,则等待对应时间再并行启动父线程
  • join可将并行变成串行

threading.local类

  • threading.local()实例可在不同不同线程中安全地使用线程独有的数据,做到线程间数据隔离,如同本地变量一样安全

定时器 Timer/延迟执行

  • threading.Timer(interval, function, args=None, kwargs=None)
  • 继承自Thread类
  • interval用于定义延时,调用start后可会在等待对应时间后延迟执行
  • 在function函数还未执行之前,可用cancel方法取消执行
  • 代码实例 :
import threading
import time
import logging

FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)

def worker():
    logging.info('in worker')
    time.sleep(2)
    logging.info('in worker')


t=threading.Timer(3,worker)
t.setName('w1')
#t.cancel() #若start前有cancel 直接停止
t.start()
time.sleep(3.1)#t进程延迟3秒启动 在3秒内都是可以取消的 超过3秒则无法取消
t.cancel()

线程同步

  • 线程同步:通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成数据操作
  • 不同操作系统实现技术有所不同 :
    • 临界值(Critical Section)
    • 互斥量(Mutex)
    • 信号量(Semaphore)
    • 事件(Event)
  • Event介绍 :
Init signature: threading.Event()
Docstring:
Class implementing event objects.

Events manage a flag that can be set to true with the set() method and reset
to false with the clear() method. The wait() method blocks(阻塞) until the flag is
true.  The flag is initially(最初) false.
  • 代码实例 :
from threading import Event,Thread
import time
import logging

FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)

def boss(event:Event,count):
    logging.info('boss : I am boss, waiting for {} cups'.format(count))
    event.wait()
    logging.info('boss : Good job')

def worker(event:Event,count):
    logging.info('worker : I am worker, working for {} cups'.format(count))
    cups=[]
    while True:
        logging.info('worker : made 1')
        time.sleep(0.1)
        cups.append(1)
        if len(cups)==count:
            event.set()
            break
    logging.info('worker : I have made {} cups'.format(len(cups)))
e=Event()
c=10
w=Thread(target=worker,args=(e,c))
b=Thread(target=boss,args=(e,c))
b.start()
w.start()
  • wait介绍 :
Signature: threading.Event.wait(self, timeout=None)
Docstring:
Block until the internal flag is true.

If the internal flag is true on entry, return immediately. Otherwise,
block until another thread calls set() to set the flag to true, or until
the optional timeout occurs.

When the timeout argument is present and not None, it should be a
floating point number specifying a timeout for the operation in seconds
(or fractions thereof).

This method returns the internal flag on exit, so it will always return
True except if a timeout is given and the operation times out.
  • 代码实例 :
from threading import Event,Thread
import logging

logging.basicConfig(level=logging.INFO)
def do(event,interval):
    while not event.wait(interval):
        logging.info('do sth')

e=Event()
Thread(target=do,args=(e,3)).start()
e.wait(10)
e.set()
print('end')

#do作为子线程与主线程并行执行,do线程每隔3秒等一次.
#主线程运行10秒后,将e设置为False,do线程结束
#主线程也随之结束
  • 用Event实现Timer :
from threading import Thread,Event

def add(x,y):
    print(x+y)

class Timer:
    def __init__(self,interval,func,args=None,kwargs=None,name=None,*,daemon=None):
        self.interval=interval
        self.event=Event()
        self.thread=Thread(target=func,args=args,kwargs=kwargs,name=name,daemon=daemon)

    def start(self):
        Thread(target=self.run).start()

    def run(self):
        if not self.event.wait(self.interval):
            self.thread.start()
            self.event.set()

    def cancel(self):
        self.event.set()

t=Timer(2,add,(8,2))

t.start()
e=Event()
e.wait(1.9)
t.cancel() #等待1.9秒 可以取消

锁/Lock

  • 在资源争抢的场景下,为保证只有一个使用者可以完全使用某一资源.就必须加锁
  • 锁适用于读写同一资源的场景
  • Lock介绍
class lock(builtins.object)
 |  A lock object is a synchronization primitive.  To create a lock,
 |  call threading.Lock().  Methods are:
 |
 |  acquire() -- lock the lock, possibly blocking until it can be obtained
 |  release() -- unlock of the lock
 |  locked() -- test whether the lock is currently locked
 |
 |  A lock is not owned by the thread that locked it; another thread may
 |  unlock it.  A thread attempting to lock a lock that it has already locked
 |  will block until another thread unlocks it.  Deadlocks may ensue(接踵而至).
 |......
  • 将1000个杯子的任务交给10个工人,代码如下:
from threading import Event,Thread,Lock
import time
import logging

FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)

cups=[]
def worker(count):
    logging.info('worker : I am worker, working for {} cups'.format(count))
    while len(cups)<count:
        logging.info('worker : made 1')
        time.sleep(0.0001)
        cups.append(1)
    logging.info('worker : We have made {} cups'.format(len(cups)))

for _ in range(10):
    Thread(target=worker,args=(1000,)).start()
  • 以上代码为多线程操作,判断失效,多生产了杯子
  • 为保证数据准确,需要加锁
  • 用锁的注意事项 :
    1. 尽量少用锁.用了锁多线程访问被锁的资源就是串行因此应少用锁,并且加锁时间越短越好
    2. 上锁(acquire)以后一定记得要解锁(release),否则会造成死锁
      • 若锁内代码异常退出,没调用release方法也会造成死锁
      • 此时应该用try或with语句执行锁内的代码
    3. 未上锁状态调用解锁方法会报错
lock=threading.Lock()
lock.acquire()
锁内的代码
lock.release()
with lock:
    锁内的代码
  • 将10个工人生产1000个杯子的任务改成加锁的版本 :
from threading import Event,Thread,Lock
import time
import logging

FORMAT='%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT,level=logging.INFO)

cups=[]
lock=Lock()
def worker(count):
    logging.info('worker : I am worker, working for {} cups'.format(count))
    finished=False
    while True:
        # lock.acquire()
        with lock:
            if len(cups)>=count:
                finished=True
                break
            if not finished:
                logging.info('worker : made 1')
                time.sleep(0.0001)
                cups.append(1)
    logging.info('worker : We have made {} cups'.format(len(cups)))

for _ in range(10):
    Thread(target=worker,args=(1000,)).start()
  • 非阻塞锁
  • acquire默认为阻塞状态 ,即访问已被其它进程锁定的资源会进入等待状态
  • 若设置为非阻塞,则不会等待
  • Lock().acquire(False)

RLock

  • RLock介绍 :
Signature: threading.RLock(*args, **kwargs)
Docstring:
Factory function that returns a new reentrant(再次进入的) lock.

A reentrant lock must be released by the thread that acquired it. 
Once a thread has acquired a reentrant lock, the same thread may acquire it again without blocking; 
the thread must release it once for each time it has acquired it.
  • RLock可多次锁定,前提是在同一线程内.锁几次,最后释放几次,才算完全释放
  • 跨线程上锁时,前一线程未完全释放,当前线程依旧会阻塞
  • 未上锁状态调用解锁方法会报错
  • 打印RLock实例时可看到owner与count值,分别对应上锁的线程ID与上锁的次数,未锁定状态二者的值为0
  • 实例代码
rl=RLock()
print(1,rl,threading.current_thread().ident)

rl.acquire()
print(2,rl)

rl.acquire()
print(3,rl)

rl.release()
print(4,rl)

rl.release()
print(5,rl)

运行结果如下:
1 <unlocked _thread.RLock object owner=0 count=0 at 0x0000028F6F020328> 3692
2 <locked _thread.RLock object owner=3692 count=1 at 0x0000028F6F020328>
3 <locked _thread.RLock object owner=3692 count=2 at 0x0000028F6F020328>
4 <locked _thread.RLock object owner=3692 count=1 at 0x0000028F6F020328>
5 <unlocked _thread.RLock object owner=0 count=0 at 0x0000028F6F020328>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值