第14章 并发编程

当前自编代码有时受网速限制,尚未受系统资源限制,暂时用不上并发编程

概述

同一CPU在某时间点只能执行一个程序,由于速度快,容易产生多个程序同时执行的错觉

并发Concurrency多进程快速轮换
并行Parallel同一时刻,多指令在多个处理器上同时执行

进程的特征

  1. 独立性。有独立的地址空间
  2. 动态性。是活动中的指令集合(程序是静态的指令集合),有自己的生命周期、状态
  3. 并发性。多个进程可以在同一处理器上并发执行,互不影响

线程的特征

  1. 同一进程下可以包含多个线程,各线程共享该进程的系统资源
  2. 其调度、管理由进程完成,无须经过操作系统
  3. 有自己的堆栈、程序计数器、局部变量
  4. 其运行是抢占式的;主线程与分线程并发,各线程的执行没有先后顺序

应用场景

当一个程序向不同客户端提供服务时,各客户端之间应该互不干扰。此时,可以在同一进程(程序)下运行多个线程(客户端)。与多进程相比,多线程共享内存、数据、代码,可以节约资源,提高运行效率,通信也更容易。

# 线程的创建和启动

threading模块下的方法

方法current_thread()Thread(target=test, args=(,))
作用获取当前线程创建线程对象

线程的方法

方法作用
getName()获取当前线程的名称
setName()为线程设置名称
is_alive()判断线程是否死亡。新建、死亡状态为False,其它状态为True
  • 默认名称:主线程MainThread,子线程Thread-1, …。名称相关操作也可用name属性实现

创建线程

Thread(group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)

参数grouptargetargskwargsdaemon
含义线程组目标方法方法的位置参数方法的关键词参数是否为后台线程
import threading
def test(a):
    x = threading.current_thread().getName()
    print(x, a)
t1 = threading.Thread(target=test, args=(1,))
t1.start()
t2 = threading.Thread(target=test, args=(2,))
t2.start()

线程的生命周期

NewReadyRunningBlockedDead
新建就绪运行阻塞死亡

调用start()方法后,线程获得栈、程序计数器,进入Ready状态,运行时机受线程调试器管理

注意:只能对新建的线程调用strat()方法,重复调用会报错,线程死亡后无法调用

不要调用run()方法,否则,只有主线程会被执行

运行和阻塞状态

阻塞的原因——阻塞解除时,线程恢复到Ready状态

  1. sleep() 主动放弃处理器资源
  2. 调用了阻塞式I/O方法
  3. 尝试获得的锁正被其它线程占用
  4. 等待Notify中

线程死亡

  1. 函数执行完成,线程正常结束
  2. 程序报错

控制线程

join(timeout=None)

一般情况下,主线程执行完成后就退出了,如果某分线程用了join,主线程等待该分线程结束后再结束。可以设置时间限制,到时间后分线程进入死亡状态(结束)

t1 = threading.Thread(target=test, args=(1,))
t1.start()
t1.join()

后台线程 Daemon Thread

主线程默认是前台线程。设置后台线程的方法:t1.daemon = True。后台线程随前台线程死亡

前台线程的子线程默认是前台线程;后台线程的子线程默认是后台线程

线程睡眠 sleep(secs)

来自time模块,进入blocked状态

# 线程同步

线程安全问题

由于系统的线程调度具有随机性,有时候需要让子线程锁定共享资源

例:银行账户余额1000元,同一时刻,在A、B两个客户端,登录同一账号,各取钱800元。余额变成-600。这明显是不合理的

Lock与RLock

锁:独占共享资源,访问前申请,访问后释放。加锁——修改——释放锁

Lock 基本锁,只能锁定一次

RLock 可重入锁,可锁定多次。其中,acquire()与release()成对出现

def test():
    self.lock.acquire()
    try:
        ...
    finally:
        self.lock.release()

死锁

两个线程互相等待对方释放锁,程序无法继续。避免死锁的方法:

  1. 对同一线程,避免多次锁定
  2. 固定加锁顺序
  3. 使用定时锁,到时间后自动释放 acquire(timeout=…)
  4. 死锁检测
    阻塞与死锁的区别:阻塞——等待;死锁——报错

# 线程通信

Condition

使占有锁却无法继续执行的线程释放锁,唤醒处于等待状态的线程。总是有对应的Lock对象

方法作用
acquire([timeout])
release()
wait([timeout])让线程进入等待池并释放锁,等待唤醒
notify()随机唤醒等待池中的一个线程,调用acquire()尝试加锁
notify_all()唤醒等待池中的所有线程

Queue

三种队列queue.Queue()queue.LifoQueue()PriorityQueue()
特征先进先出后进先出优先级队列
  • 共有参数:队列的大小maxsize=0 ,<=0时不限制,达到上限时会加锁,不能再加入元素

队列的方法

方法(队列的状态)qsize()empty()full()
作用元素数量队列是否为空队列是否已满
方法参数作用
put()item, block=True, timeout=None放入元素
get()提取/消费元素
put_nowait()item放入元素,不阻塞
get_nowait()item提取/消费元素,不阻塞

block:队列已满、已空时的操作。已满+True——阻塞;已空+False——报异常

timeout:阻塞时间,None表示长期,直到该队列的元素被消费

Event

不带Lock对象,想实现线程同步时,需要额外的Lock对象

is_set() 返回是否为True

set() 设置为True,并唤醒所有处于等待状态的线程

clear() 设置为False

wait([timeout=None]) 阻塞

# 线程池

用途:大量创建生存期很短的线程,可用【最大线程数】控制并发数量,保证系统性能

步骤:创建线程池——定义函数fn——提交任务submit——关闭线程池shutdown()

concurrent.futures模块下的Executor

创建线程池:ThreadPoolExecutor

创建进程池:ProcessPoolExecutor

方法参数作用
submit()fn, *args, **kwargs返回Future对象
map()fn, *iterables, timeout=None, chunkxize=1启动多个线程,
异步对iterables进行map处理,
类似全局函数map(fn, *iterables)
shotdown()wait=True关闭线程池
  • 【args】为位置参数,【kwargs】为关键词参数

Future对象的方法

线程状态cancelled()running()done()
True已取消正在运行执行完成或成功取消
方法作用
cancle()取消线程
result(timeout=None)获取线程的最终结果
exception(timeout=None)获取线程的异常
add_done_callback(fn)为线程注册回调函数,线程完成时触发fn
shutdown()关闭线程池。已经提交的任务继续执行,新任务不再接收

使用线程池

  1. 创建线程池ThreadPoolExecutor
  2. 定义函数fn
  3. 提交任务submit()
  4. 关闭线程池shutdown()

获取执行结果

方法是否阻塞主线程
result()
add_done_callback(fn)

线程相关类

线程局部变量

threading模块下的local()函数:返回一个(线程的)局部变量,相当于各线程各有一个可以独立地修改的副本变量。

线程的功能作用
同步机制并发访问共享资源时,各线程进行通信
局部变量隔离多个线程间的共享冲突

定时器

Thread类下的Timer子类,让指定函数在特定时间内执行一次

from threading import Timer
def test(): print(1)
t = Timer(3,test)
t.start()

注:Timer对象有cancel函数

任务调度

任务调度器:sched模块下的scheduler类

sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep)

imefunc:生成时间戳的时间函数

delayfunc阻塞程序的函数

属性:queue 返回调度器的调度队列

方法作用
enterabs()time, priority, action, argument=(), kwargs={}返回一个event,可用cancel()方法取消调度
enter()delay, priority, action, argument,=(), kwargs={}同上
cancel()event取消当前调试队列中的一个任务
empty()判断当前调度队列是否为空
run()blocking=True运行所有需调度的方法

time:时间点

priority:优先级:同一时间点,有多个任务时,优先级高(值小)的先执行

argument:位置参数

delay:若干秒后开始执行action任务

blocking=True:阻塞线程,直到所有被调度的方法执行完成

# 多进程

创建新进程 multiprocessing.Process

属性namedaemonpidauthkey
进程的名称后台状态id授权key
方法run()start()join([timeout])is_alive()terminate()
作用中断

Context和启动进程的方式

multiprocessing.set_start_method('spawn')  #写在多进程相关代码之前
...
a = multiprocessing.get_context('spawn')   #创建Context对象
q = a.Queue()                              #启动进程
mp = a.Process(target=test, args = (1,))
mp.start()

用进程池管理进程 multiprocessing.Pool()

进程池常用方法

方法作用
close()关闭进程池。不再接受新任务,执行完池中进程后关闭
terminate()立即中止进程池
join()等待所有进程完成

用进程处理函数

方法参数作用阻塞
apply()func, [args], [kwds]将func交给进程池处理
apply_async()func, [args], [kwds], [callback], [error_callback]
map()func, iterable, [chunksize]用新进程对iterable的每个元素执行func
imap()
map_async()func, iterable, [chunksize], [callback], [error_callback]
  • imap()为map()的延迟版本

进程通信

进程通信用mutiprocessing.Queue()

线程通信用queue.Queue()

get():读取数据

put(data):放入数据

创建管道 multiprocessing.Pipe()

管道的方法

发送与接收作用
send(obj)向管道的另一端发送数据,大小限制在32MB
send_bytes(buffer, [offset], [size])发送字节数据,默认发送全部,可设置起点、字节长度
recv()接收send()发送的数据
recv_bytes([maxlength])接受send_bytes()发送的数据,可设置字数上限
recv_bytes_into(buffer, [offset])接收到的数据放在buffer中
其它方法作用
fileno()连接用的文件描述器
poll([timeout])返回连接中是否还有数据可读取
close()关闭连接
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ailsa2019

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值