一、并发 1、并行:同时间点,不同的事情都在发生,不同跑道 2、并发:在同时间,有很多事情要做,怎么做做什么不知道 并行可以解决并发的问题 二、并发的解决 1、队列,缓冲区 1、排队解决,就是队列,排成的队列,就是缓冲区 2、优先队列,插队或者另开口子优先 2、争抢 1、抢到了就视为锁定窗口,别人不能抢占资源,锁机制 2、坏处,抢不到的可能很长时间都没解决 3、预处理 1、一种提前加载用户需要数据的思路,预处理思想,缓存常用 2、数据以去年,昨天,上周这种的规模来比较 4、并行 1、日常可以通过购买服务器,或多开进程、线程实现并行处理,水平扩展 2、如果线程在单CPU上处理,就不是并行 5、提速 1、提高单个CPU性能,或单个服务器安装更多的CPU 2、一般称作垂直扩展,但这种瓶颈太明显,后期成本太高 6、消息中间件,系统之外的东西 1、消息在半路就缓冲,类似于上地门外的环状铁圈,让人们缓冲进入 2、RabbitMQ,kfk等
二、进程和线程 1、一个程序执行的实例就是进程 2、程序是源代码编译的文件,文件被加载到内存中,变成了进程,进程存放指令和数据,也是线程的容器 3、Linux进程有父进程,子进程,Windows是平等关系 4、线程,是程序执行流的最小单位,标准线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成 5、每一个进程都认为自己独占所有计算机硬件资源,虚拟的 进程是独立的王国,进程间不可以随便共享数据 线程就是省份,同一进程线程可以共享进程的资源,线程拥有自己独立的堆栈
三、线程的状态 1、运行态 该时刻,该进程正在占用CPU 2、就绪态 可随时转换为运行态,因为其他进程正在运行而暂停,不占用CPU 3、阻塞态 除非某些外部事件发生,否则进程不运行
四、Python中的线程开发 1、threading库 1、Thread类 def init(self, group=None, target=None, args=( ), kwargs=None, *, daemon=None)
线程启动
import threading
def worker():
print('I am working.')
print('Finished')
t = threading.Thread(target=worker, name='worker')
t.start()
2、python中没有线程退出的方法
执行完毕
异常
import threading
import time
def worker():
count = 0
while True:
count += 1
if count > 5:
raise RuntimeError(count)
time.sleep(1)
print('I am working.')
t = threading.Thread(target=worker, name='worker')
t.start()
print('End')
3、python线程没有优先级,没有线程组的该奶奶,也不能被销毁,停止,挂起,也没有恢复和中断
4、start会执行run方法,但是run只是个函数,类似于call方法,不是多线程
5、线程传参和函数一样
threads1 = threading.Thread(target=add,name='add', args=(1,),kwargs={'y':4} )
6、属性和方法
名称 含义 current_thread( ) 返回当前线程对象 main_thread() 返回主线程对象 active_count( ) 当前处于alive状态的线程个数 enumerate() 返回所有活着的线程的列表,不包括已终止的线程和未开始的线程 get_ident() 返回当前线程的ID,非0整数 import threading import time def showthreadinfo(): print('current_thread = {}'.format(threading.current_thread())) print('main thread = {}'.format(threading.main_thread())) print('active count = {}'.format(threading.active_count())) def worker(): count = 0 showthreadinfo() while True: if count > 5: break time.sleep(1) count += 1 print('I am working') t = threading.Thread(target=worker, name='worker') showthreadinfo() t.start() print('End') 7、Thread实例的属性和方法
名称 含义 name 只是个名字,标识,可以重名,getName( ),setName( )获取、设置这个名词 ident 线程ID,它是非0整数。线程启动后才会有ID,否则为None,线程退出,此ID依旧可以访问,此ID可以重复使用 is_alive( ) 返回线程是否或者 注意:线程的name这是一个名称,可以重复,ID必须唯一,但可以在线程退出后再利用 import threading import time def worker(): count = 0 while True: if count > 5: break time.sleep(1) count += 1 print(threading.current_thread().name) t = threading.Thread(target=worker, name='worker') print(t.ident) t.start() while True: time.sleep(1) if t.is_alive(): print('{} {} alive'.format(t.name, t.ident)) else: print('{} {} dead'.format(t.name, t.ident)) t.start() # 报错,只能启动一次
名称 含义 start() 启动线程每一个线程必须必须且只能执行该方法一次 run() 运行线程函数 为了演示,派生一个Thread的子类 import threading import time def worker(): count = 0 while True: if count > 5: break time.sleep(1) count += 1 print('worker running') class MyThread(threading.Thread): def start(self): print('start') super().start() def run(self): print('run') super().run() t = MyThread(name='worker', target=worker) # t.start() t.run() 启动新的线程,要用start,run只是在主线程调用普通函数
五、多线程 1、主线程之外,再启动一个,就是多线程了 import threading import time def worker(): count = 0 while True: if count > 5: break time.sleep(0.5) count += 1 print('worker running') print(threading.current_thread().name, threading.current_thread().ident) class MyThread(threading.Thread): def start(self): print('start') super().start() def run(self): print('run') super().run() t1 = threading.Thread(name='worker1', target=worker) t2 = threading.Thread(name='worker2', target=worker) t1.start() t2.start()
t1和t2交替执行,run方法不可以
一个进程至少有一个线程,并作为程序的入口,这个线程就是主线程,一个进程至少有一个主线程
六、线程安全 线程执行一段代码,不会产生不确定的结果,那这段代码就是线程安全的 1、线程可以被打断,不安全 import threading def worker(): for _ in range(100): print('{} is running.'.format(threading.current_thread().name)) for x in range(1,5): name = 'worker{}'.format(x) t = threading.Thread(name=name, target=worker) t.start() 在ipython中可以看到,print语句被线程切换打断,print在打印之后要换行,在这中间发生了线程切换 这说明print函数是线程不安全的
2、不让print换行,就把\n加到字符串内,结尾不换行,字符串拼接end=''
3、使用logging模块
标准库里面的logging模块,日志处理模块,线程是安全的,生产环境代码都是用logging
import threading
import logging
def worker():
for _ in range(100):
logging.warning('{} is running.'.format(threading.current_thread().name))
for x in range(1, 5):
name = 'worker{}'.format(x)
t = threading.Thread(name=name, target=worker)
t.start()
七、daemon线程和non-daemon线程 1、non-daemon是不关闭主线程,会一直跑子线程 2、daemon是可以关闭主线程,会直接杀掉,如果遇见了non-daemon会等待,non-daemon一结束,daemon立刻杀掉 3、不能缺少的要用non-daemon,不关心的可以有始无终的可以用daemon 4、父线程:如果线程A中启动了一个线程B,A就是B的父线程 子线程:B就是A的子线程 5、Python中,构造线程的时候,可以设置daemon属性,这个属性必须在start前设置好。 线程daemon属性,如果设定就是用户的设置,否则就取当前线程的daemon值,主线程是non-daemon线程,即daemon=False
import threading
import time
def foo():
time.sleep(3)
for i in range(20):
print(i)
t = threading.Thread(target=foo, daemon=False)
t.start()
print('Main Thread Exiting')
如果daemon换成True,t线程不会被执行
名称 含义 daemon属性 表示线程是否是daemon线程,这个值必须在start( )之前设置,否则引发RuntimeError异常 isDaemon() 是否是daemon线程 setDaemon 设置未daemon线程,必须在start方法之前设置 6、总结: 线程具有一个daemon属性,可以显示设置为True或False,也可以不设置,默认值为None 如果不设置daemon,就取当前线程的daemon来设置它。 主线程是non-daemon线程,即daemon=False Python程序在没有活着的non-daemon线程运行时退出,也就是剩下的只能时daemon线程,主线程才能退出,否则主线程就只能等待 import time import threading
def bar():
time.sleep(3)
print('bar')
def foo():
for i in range(20):
print(i)
t = threading.Thread(target=bar, daemon=False)
t.start()
t = threading.Thread(target=foo, daemon=True)
t.start()
print('Main Thread Exiting')
上例不能打印bar,因为执行不到bar这个线程就结束了
下面加上time.sleep就可以执行到bar
import time
import threading
def foo(n):
for i in range(n):
print(i)
time.sleep(1)
t1 = threading.Thread(target=foo, args=(10,), daemon=True)
t1.start()
t2 = threading.Thread(target=foo, args=(20,), daemon=False)
t2.start()
time.sleep(2)
print('Main Thread Exiting')
上例因为non-daemon,所以主线程退出时,也不会杀掉所有daemon线程,而是等non-daemon线程全部结束,如果还有daemon线程,主线程就会杀掉daemon线程,退出
7、应用场景
1、后台任务。如心跳包发送,监控,这种场景最多
2、主线程工作才有用的线程,如主线程中维护着共公共资源,主线程结束了,资源也没了意义,一起退出最合适
3、随时可以被终止的线程
八、join
import time
import threading
def foo(n):
for i in range(n):
print(i)
time.sleep(1)
t1 = threading.Thread(target=foo, args=(10,), daemon=True)
t1.start()
t1.join()
print('Main Thread Exiting')
会一直执行完,主线程阻塞,最后输出print
1、join类似于等待,谁调用join谁等待,t.join( )可以当作join(t),是主线程调用join
2、一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用结束,一个线程可以被join多次
import time
import threading
def bar():
while True:
time.sleep(1)
print('bar')
def foo():
print("t1's daemon ={}".format(threading.current_thread().isDaemon()))
t2 = threading.Thread(target=bar)
t2.start()
print("t2's daemon = {}".format(t2.isDaemon()))
t2.join() 没用,都是daemon
t1 = threading.Thread(target=foo, daemon=True)
t1.start()
t1.join()主线程等待,不停止t1
time.sleep(3)
九、threading.local类 import time import threading
def worker():
x = 0
for _ in range(100):
time.sleep(0.0001)
x += 1
print(threading.current_thread(), x)
for i in range(10):
threading.Thread(target=worker).start()
局部变量,所以多线程的结果是,x都是100
import time
import threading
class A:
def __init__(self):
self.x = 0
global_data = A()
def worker():
global_data.x = 0
for i in range(100):
time.sleep(0.0001)
global_data.x += 1
print(threading.current_thread(), global_data.x)
for i in range(10):
threading.Thread(target=worker).start()
这几个线程每次开启,都会重置全局变量x,然后叠加,最后在980以上,而且每个都不一样,线程互相干扰
如何使用全局变量,但是线程互相不干扰 threading.local类 import time import threading
global_data = threading.local()
def worker():
global_data.x = 0
for i in range(100):
time.sleep(0.0001)
global_data.x += 1
print(threading.current_thread(), global_data.x)
for i in range(10):
threading.Thread(target=worker).start()
设置变量被局部变量后,各线程互不干扰 但是只有设置的局部才可以用,其他的全局设置,都不可以 threading.local类构建了一个大字典,其元素是每一个线程的key和线程对象引用线程单独的字典的映射
十、Timer定时器/延迟执行
threading.Timer继承自Thread,用来定义多久执行一个函数
class threading.Timer(interval, function, args=None, kwargs=None)
start执行后,Timer对象会等待,等待了interval之后,开始执行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)
t = threading.Timer(5, worker)
t.setName('w1')
t.start()
print(threading.enumerate())
# t.cancel()
time.sleep(1)
print(threading.enumerate())
5秒后打印log,2秒后,结束 如果线程中worker函数已经开始执行,cancel就没有效果了 总结 Timer是线程Thread的子类,就是线程类,具有线程的能力和特征 它的实例是能够延迟执行目标函数的线程,再真正执行目标函数之前,都可以cancel它
十一、logging模块 1、日志级别
日志级别level
数值
CRITICAL
50
ERROR
40
WARNING
30,默认级别
INFO
20,发出来看信息
DEBUG
10,一般不开
NOTSET
0调用debug(),info( ),warning( ),error( ),critical( )方法
2、格式化字符串
属性名
格式
描述
日志消
息内容
%(message)s
The logged message,computed as msg.
当调用Formatter.format( )时设置
asctime
%(asctime)s
创建LogRecord时的可读时间。默认情况下,
y-m-d H:M:S,879,逗号后是毫秒部分时间
函数名
%(funcName)s
日志调用所在的函数名
日志级别
名称
%(levelname)s
消息的级别名称"DEBUG","INFO"
"WARNING","ERROR","CRITICAL"
日志级别
数值
%(levelno)s
消息的级别数字
行号
%(lineno)d
日志调用所在的源码行号
模块
%(moudle)s
模块(filename的名字部分)
进程ID
%(process)d
进程ID
线程ID
%(thread)d
线程ID
进程名称
%(processName)s
进程名
线程名称
%(threadName)s
线程名字
举例:
默认级别
import logging
FORMAT = '%(asctime)-15s Thread info: %(thread)d %(threadName)s %(message)s'
logging.basicConfig(format=FORMAT)
logging.warning('I am {}'.format(20))
构建消息
import logging
FORMAT = '%(asctime)-15s Thread info: %(thread)d %(threadName)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
logging.info('I am {}'.format(20))
logging.info('I am %d %s', 20, 'years old') c风格的
上例是基本使用方法,大多数时候,用的是info,正常运行信息的输出
import logging
FORMAT = '%(asctime)-15s Thread info: %(thread)d %(threadName)s %(message)s %(school)s'
logging.basicConfig(format=FORMAT, level=logging.WARNING)
d = {'school': 'magedu.com'}
logging.info('I am {}'.format(20), extra=d)
logging.warning('I am %d %s', 20, 'years old', extra=d)
使用一个字典d,extra传入到logging中
1、修改日期格式
import logging
logging.basicConfig(format='%(asctime)s %(message)s', datefmt="%Y/%m/%d %I:%M:%S" )
logging.warning('this event was logged.')
2、输出到文件
import logging
logging.basicConfig(format='%(asctime)s %(message)s', filename='D:/QQLive/test.log')
for _ in range(5):
logging.warning('this event was logged.')
十二、Logger类 1、构造 使用工厂(format)方法返回一个Logger实例 logging.getLogger([name=None]) 指定name,返回一个名称为name的Logger实例。如果再次使用相同的名字,是实例化一个对象。 未指定name,返回Logger实例,名称是root,即根Logger。 Logger是层次结构的,使用.点号分割,如'a'、'a.b',或'a.b.c.d',a是a.b的父,a.b是a的child,对于foo来说,名字为,foo.bar,foo.bar.baz,foo.bam都是foo的后代 创建一个basic.Config后,再次设置,不管用,因为创建了一个列表,不为空,就不会重置format,需要重启
import logging
logging.basicConfig(format='%(asctime)-15s\tThread info: %(thread)d %(threadName)s %(message)s', level=logging.INFO)
root = logging.getLogger() # 不写就是root
print(root.name, type(root), root.parent, id(root))
# logger = logging.getLogger(__name__)
# print(logger.name, type(logger), id(logger), id(logger.parent))
logger1 = logging.getLogger('a') 以root为父
print(logger1.name, type(logger1), id(logger1), id(logger1.parent))
logger2 = logging.getLogger('a.b') 以a为父
print(logger2.name, type(logger2), id(logger2), id(logger2.parent))
logger3 = logging.getLogger('c') 以root为父
print(logger3.name, type(logger3), id(logger3), id(logger3.parent))
2、级别设置
import logging
logging.basicConfig(format='%(asctime)-15s\tThread info: %(thread)d %(threadName)s %(message)s', level=logging.INFO)
logger = logging.getLogger(__name__)
print(logger.name, type(logger))
print(logger.getEffectiveLevel())
logger.info('hello1')
logger.setLevel(28) logger设置28,不影响它的父logging,即root
print(logger.getEffectiveLevel())
logger.info('hello2') # 打印不出来,因为是28
logger.warning('warning')
logging.info('hello3') 还可以打印出来
各自设置各自的level,父子之间各自管理自己level
3、Handler
handler控制日志信息的输出目的地,可以是控制台,文件。
可以单独设置level
可以单独设置格式
可以设置过滤器
Handler
StreamHandler # 不指定使用sys.stderr
FileHandler # 文件
_StderrHandler # 标准输出
NullHandler # 什么都不做
1、举例
level的继承
import logging
logging.basicConfig(format='%(asctime)-15s\tThread info: %(thread)d %(threadName)s %(message)s', level=logging.INFO)
root = logging.getLogger()
log1 = logging.getLogger('s')
log1.setLevel(logging.ERROR)
log2 = logging.getLogger('s.s1')
log2.warning('log2 warning') log2继承最近的祖先的level
2、Handler
import logging
logging.basicConfig(format='%(asctime)-15s\tThread info: %(thread)d %(threadName)s %(message)s', level=logging.INFO)
root = logging.getLogger()
log1 = logging.getLogger('s')
log1.setLevel(logging.WARNING)
h1 = logging.StreamHandler()
h1.setLevel(logging.INFO)
log1.addHandler(h1)
log2 = logging.getLogger('s.s1')
print(log2.getEffectiveLevel())
h2 = logging.StreamHandler()
h2.setLevel(logging.ERROR)
log2.addHandler(h2)
log2.warning('log2 warning')
log2.error('log2 error')
总结;
在某个logger上产生某种级别的消息,首先和logger的level检查,如果消息级别低于logger的级别,丢弃,如果通过,交给logger所有的handler处理,每一个handler需要用自己的level和消息的level比较是否处理,如果没有handler,或者消息已经被handler处理,则继续发送给父logger处理,父logger拿到消息,重复第三条,直到根logger
4、Formatter
loggingd的Formatter类,它允许指定某个格式的字符串,如果提供None,那么%(message)s将会作为默认值
下面例子看起来更明显,输出了格式化的字符串 import logging
logging.basicConfig(format='%(asctime)-15s\tThread info: %(thread)d %(threadName)s %(message)s', level=logging.INFO)
log1 = logging.getLogger('s')
log1.setLevel(logging.WARNING)
h1 = logging.StreamHandler()
h1.setLevel(logging.INFO)
ftm1 = logging.Formatter('log1-h1 %(message)s')
h1.setFormatter(ftm1)
log1.addHandler(h1)
log2 = logging.getLogger('s.s1')
print(log2.getEffectiveLevel())
h2 = logging.StreamHandler()
h2.setLevel(logging.ERROR)
ftm2 = logging.Formatter('log2-h2 %(message)s')
log2.addHandler(h2)
log2.warning('log2 warning')
log2.error('log2 error')
5、Filter
可以为Handler增加过滤器,过滤器只影响一个handler,不会影响整个处理流程
logger add handler handler add formatter handler add filter
import logging
logging.basicConfig(format='%(asctime)-15s\tThread info: %(thread)d %(threadName)s %(message)s', level=logging.INFO)
log1 = logging.getLogger('s') log1.setLevel(logging.WARNING) h1 = logging.StreamHandler() h1.setLevel(logging.INFO) ftm1 = logging.Formatter('log1-h1 %(message)s') h1.setFormatter(ftm1) log1.addHandler(h1) log2 = logging.getLogger('s.s1') print(log2.getEffectiveLevel()) h2 = logging.StreamHandler() h2.setLevel(logging.ERROR) ftm2 = logging.Formatter('log2-h2 %(message)s') h2.setFormatter(ftm2) f2 = logging.Filter('s') h2.addFilter(f2) log2.addHandler(h2) log2.warning('log2 warning')
消息log2的,它的名字是s.s1,因此过滤器名字设置为s或者s.s1,消息就可以通过,但是如果是其他,就不能通过,不设置的话,全部通过
十三、线程同步 1、线程间的协作,一个线程访问某些数据时,其他线程不能访问,直到该线程完成对数据的操作 2、Event,通过使用一个内部的标记flag,通过flag的True或False的变化,来进行操作
名称 含义 set( ) 标记设置为True clear( ) 标记设置为False is_set( ) 标记是否为True wait(timeout=None) 设置一个时长,表示等待标记flag变为True多久,如果只能1秒,1秒返回True,就是等了1秒,返回True,None为无限等待,直到 等到了flag变为True,则返回True,如果设置了时间,而且未等到超过时间返回False from threading import Thread, Lock, Event import logging import threading import time
FORMAT = '%(asctime)s %(threadName)s %(message)s %(thread)d'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def boss(event: Event):
logging.info('waiting')
event.wait()
logging.info('Good')
def worker(event: Event, count=10):
logging.info('working')
cups = []
while True:
logging.info('make 1')
time.sleep(0.5)
cups.append(1)
if len(cups) >= count:
event.set()
break
logging.info('finished')
event = Event()
w = Thread(target=worker, name='worker', args=(event,))
b = Thread(target=boss, name="boss", args=(event,))
w.start()
b.start()
两者使用同一个event对象的标记flag。
谁wait就是等待flag变成True,或等到超时返回False,继续执行下面的函数,不限制等待的个数
from threading import Thread, Lock, Event
import logging
FORMAT = '%(asctime)s %(threadName)s %(message)s %(thread)d'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def do(event: Event, interval: int):
while not event.wait(interval):
logging.info('do sth.')
e = Event()
Thread(target=do, args=(e, 3)).start()
e.wait(10)
e.set()
print('main exit')
需求:实现Timer,延时执行的线程
延时计算add(x,y)
from threading import Thread, Lock, Event
import logging
import threading
import time
import datetime
FORMAT = '%(asctime)s %(threadName)s %(message)s %(thread)d'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Timer:
def __init__(self, interval, fn, *args):
self.interval = interval
self.fn = fn
self.event = Event()
self.args = args
def start(self):
Thread(target=self.__run).start()
def __run(self):
start = datetime.datetime.now()
logging.info('waiting')
self.event.wait(self.interval)
if not self.event.is_set():
self.fn(*self.args)
delta = (datetime.datetime.now() - start).total_seconds()
logging.info('finished {}'.format(delta))
def cancel(self):
self.event.set()
def add(x, y):
logging.info(x + y)
t = Timer(5, add, 4, 5)
t.start()
# t.cancel()
十四、Lock 1、锁,一旦线程获得锁,其他试图获取锁的线程将被阻塞
名称 含义 acquire(blocking =True,timeout=-1) 默认阻塞,阻塞可以设置超时时间,非阻塞时,timeout禁止设置,成功获取锁,返回True,否则返回False release( ) 释放锁,可以从任何线程调用释放。 已上锁的锁,会被重置为unlocked,未上锁的锁上调用, 会报RuntimeError import threading import logging import time
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
cups = []
lock = threading.Lock()
def worker(count=10):
logging.info('working')
flag = False
while True:
lock.acquire()
if len(cups) >= count:
flag = True
break
time.sleep(0.001)
if not flag:
cups.append(1)
lock.release()
logging.info('finished {}'.format(len(cups)))
for _ in range(10):
threading.Thread(target=worker, args=(100,)).start( )
计数器,可以加,可以减
import threading
import logging
import time
class Counter:
def __init__(self):
self._val = 0
self.lock = threading.Lock()
@property
def value(self):
return self._val
def inc(self):
try:
self.lock.acquire()
self._val += 1
finally:
self.lock.release()
def dec(self):
try:
self.lock.acquire()
self._val -= 1
finally:
self.lock.release()
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 = 100
c2 = 1000
for i in range(c1):
threading.Thread(target=run, args=(c, c2)).start()
while True:
time.sleep(1)
if threading.active_count() == 1:
print(threading.enumerate())
print(c.value)
break
else:
print(threading.enumerate())
2、锁的应用场景
适用于访问和修改同一个共享资源的时候,读写同一个资源
3、非阻塞锁使用
import threading
import logging
import time
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(tasks):
for task in tasks:
time.sleep(0.001)
if task.lock.acquire(False): # 获取锁则返回True
logging.info('{} {} begin to start.'.format(threading.current_thread(), task.name))
else:
logging.info('{} {} is working.'.format(threading.current_thread(), task.name))
class Task:
def __init__(self, name):
self.name = name
self.lock = threading.Lock()
tasks = [Task('task-{}'.format(x)) for x in range(10)]
for i in range(5):
threading.Thread(target=worker, name='work-{}'.format(i), args=(tasks,)).start()
4、可重入锁
可重入锁时线程相关的锁
线程A获得可重复锁,并可以多次成功获取,不会阻塞,最后要在线程A中做相同次数的release
import threading
lock = threading.RLock()
print(lock.acquire())
print('------------')
print(lock.acquire(blocking=False))
print(lock.acquire())
print(lock.acquire(timeout=3.55))
print(lock.acquire(blocking=False))
lock.release()
lock.release()
lock.release()
lock.release()
lock.release()
# lock.release() # 多一次报RuntimeError
import threading
lock = threading.RLock()
def sub(l):
print('{}:{}'.format(threading.current_thread(), l.acquire()))
print('{}:{}'.format(threading.current_thread(), l.acquire(False)))
l.release()
l.release()
threading.Timer(2, sub, args=(lock,)).start()
print(lock.acquire())
lock.release()
5、Condition
构造方法Condition(lock=None),可以传入一个Lock或RLock对象,默认时RLock
名称 含义 acquire(*args) 获取锁 wait(self, timeout=None) 等待或超时 notify(n=1) 唤醒至多指定数目个数的等待的线程,若没有 等待的线程就没有任何操作 notify_all( ) 唤醒所有等待的线程 Condition用于生产者、消费者模型,为了解决生产者消费速度匹配问题 例子:消费者消费速度大于生产者生产速度 import threading import logging import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Dispatcher:
def __init__(self):
self.data = None
self.event = threading.Event()
def produce(self, total):
for _ in range(total):
data = random.randint(0, 100)
logging.info(data)
self.data = data
self.event.wait(1)
self.event.set() # 下面的死循环,在这里被打断
def consume(self):
while not self.event.is_set():
logging.info('received {}'.format(self.data))
self.data = None
self.event.wait(0.5)
d = Dispatcher()
p = threading.Thread(target=d.produce, args=(10,), name='producer')
c = threading.Thread(target=d.consume, name='consumer')
c.start()
p.start()
消费者主动来查,浪费大量时间,可以换成通知机制,有数据通知消费者
import threading
import logging
import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Dispatcher:
def __init__(self):
self.data = None
self.event = threading.Event()
self.cond = threading.Condition()
def produce(self, total):
for _ in range(total):
data = random.randint(0, 100)
with self.cond:
logging.info(data)
self.data = data
self.cond.notify_all()
self.event.wait(1) # 模拟生产速度
self.event.set()
def consume(self):
while not self.event.is_set():
with self.cond:
self.cond.wait() # 阻塞等通知
logging.info('received {}'.format(self.data))
self.data = None
self.event.wait(0.5) # 模拟消费速度
d = Dispatcher()
p = threading.Thread(target=d.produce, args=(10,), name='producer')
c = threading.Thread(target=d.consume, name='consumer')
c.start()
p.start()
主动通知机制和阻塞等通知
如果是1个生产者,多个消费者
import threading
import logging
import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Dispatcher:
def __init__(self):
self.data = None
self.event = threading.Event()
self.cond = threading.Condition()
def produce(self, total):
for _ in range(total):
data = random.randint(0, 100)
with self.cond:
logging.info(data)
self.data = data
self.cond.notify_all()
self.event.wait(1) # 等待一秒,开启五个线程
self.event.set() # 这句话貌似没用?
def consume(self):
while not self.event.is_set():
with self.cond:
self.cond.wait()
logging.info('received {}'.format(self.data))
self.event.wait(0.5)
d = Dispatcher()
p = threading.Thread(target=d.produce, args=(10,), name='producer')
for i in range(5):
c = threading.Thread(target=d.consume, name='consumer-{}'.format(i))
c.start()
p.start()
这个例子实现了一对多,这就是广播模式,不过程序本身不是线程安全的
Condition用于生产者消费者模型中,解决生产者消费者速度匹配的问题
采用了通知机制,非常有效率
使用方式:
使用Condition,必须先acquire,用完release,因为内部使用了RLock,所以使用with上下文最好
消费者wait,等待通知
生产者生成消息后,对消费者发通知,使用notify或notify_all( )
6、Barrier
栅栏,屏障,可以当作路障
名称 含义 Barrier(parties, action=None, timeout=None) 构建Barrier对象,指定参与方项目,timeout是wait方法,未指定超时的默认值 n_waiting 当前在屏障中等待的线程数 parties 各方数,就是需要多少个等待 wait(timeout=None) 等待通过屏障,返回0到线程数-1的整数,每个线程返回不同 如果wait方法设置了超时,并超时发送,屏障处于broken
Barrier实例
import threading
import logging
import time
import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(barrier: threading.Barrier):
logging.info('waiting for {} threads.'.format(barrier.n_waiting))
try:
barrier_id = barrier.wait()
logging.info('after barrier {}'.format(barrier_id))
except threading.BrokenBarrierError:
logging.info('Broken Barrier')
barrier = threading.Barrier(3)
for x in range(3): # 改成4、5、6试试
threading.Thread(target=worker, name='worker-{}'.format(x), args=(barrier,)).start()
logging.info('started')
运行结果可以看出:
所有线程到了Barrier前等待,直到parties的数目到了,屏障打开,继续执行
再有线程wait的话,屏障就绪,等到到达数目后再继续
举例:赛马,闸门前等待
名称 含义 broken 如果屏障处于打破状态,返回True abort( ) 将屏障至于broken状态,等待中的线程或调用等待方法的 线程,都会抛出BrokenBarrierError,直到reset方法恢复 reset( ) 恢复屏障,重新开始拦截
import threading
import logging
import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(barrier: threading.Barrier):
logging.info('waiting for {} threads.'.format(barrier.n_waiting))
try:
barrier_id = barrier.wait()
logging.info('after barrier {}'.format(barrier_id))
except threading.BrokenBarrierError:
logging.info('Broken Barrier')
barrier = threading.Barrier(3)
for x in range(0, 9):
if x == 2:
barrier.abort()
elif x == 6:
barrier.reset()
threading.Event().wait(1)
threading.Thread(target=worker, name='worker-{}'.format(x), args=(barrier,)).start()
logging.info('started')
屏障等待2个后被打破,等待的线程抛异常,新的等待的线程也抛异常,直到恢复
7,wait方法超时实例 如果wait方法,超时发生,屏障处于broken状态,直到reset import threading import logging import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(barrier: threading.Barrier, i: int):
logging.info('waiting for {} threads.'.format(barrier.n_waiting))
try:
logging.info(barrier.broken)
if i < 3:
barrier_id = barrier.wait(1)
else:
if i == 6:
barrier.reset()
barrier_id = barrier.wait()
logging.info('after barrier {}'.format(barrier_id))
except threading.BrokenBarrierError:
logging.info('Broken Barrier')
barrier = threading.Barrier(3)
for x in range(0, 9):
threading.Event().wait(2)
threading.Thread(target=worker, name='worker-{}'.format(x), args=(barrier, x)).start()
logging.info('started')
Barrier应用于并发初始化 10个线程做10个准备工作,每个线程负责一个,只有十个线程都完成后,才能继续工作,先完成的要等待后完成的线程 工作量:只需要完成一般什么的
8、semaphore信号量 和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,就会阻塞
import threading import logging import time
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s' logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(barrier: threading.Semaphore): logging.info('in sub thread') logging.info(s.acquire()) # False logging.info('sub thread over')
s = threading.Semaphore(3) logging.info(s.acquire()) # True logging.info(s.acquire()) # True logging.info(s.acquire()) # True
threading.Thread(target=worker, args=(s,)).start() time.sleep(2) logging.info(s.acquire(False)) # 非阻塞也是Fasle,因为s最多开三个锁 logging.info(s.acquire(timeout=3)) # 阻塞 logging.info('release') s.release()
应用举例 连接池,资源有限,开启连接成本高,所以使用连接池 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(x)) for x in range(self.count)] # 获取连接池数量和名字 def _connect(self, conn_name): return Conn(conn_name) # 连接名
def get_conn(self):
if len(self.pool) > 0:
return self.pool.pop()
# 拿出一个连接,不安全,判断位置
def return_conn(self, conn:Conn):
self.pool.append(conn)
使用信号量,加以修改
import threading
import logging
import random
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Conn:
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
class Pool:
def __init__(self, count: int):
self.count = count
self.pool = [self._connect("conn-{}".format(x)) for x in range(self.count)]
self.semaphore = threading.Semaphore(count)
# 获取连接池数量和名字
def _connect(self, conn_name):
return Conn(conn_name) # 连接名
def get_conn(self):
self.semaphore.acquire()
conn = self.pool.pop()
return conn
# 拿出一个连接
def return_conn(self, conn:Conn):
self.pool.append(conn)
self.semaphore.release()
pool = Pool(3)
def worker(pool: Pool):
conn = pool.get_conn()
logging.info(conn)
# 模拟使用了一段时间
threading.Event().wait(random.randint(1, 4))
for i in range(6):
threading.Thread(target=worker, name='worker-{}'.format(i), args=(pool,)).start()
资源只有三个,信号源解决,会阻塞住等待,但这个例子不能用到生成环境
如果未使用信号量,直接release,会造成信号量增加,需要绑定
9、BoundedSemaphore 信号量比列表长度好,线程安全,多还多要,都会抛出错误
信号量和锁 锁,只允许同一时间一个线程独占资源,它是特殊的信号量,信号量初始为1 信号量,允许多个线程访问共享资源,但这个共享数量有限 锁可以看作是特殊的信号量
数据结构和GIL Queue 标准库Queue模块,提供FIFO的Queue、LIFO的队列,优先队列 Queue类是线程安全的,适用于多线程安全交换数据,内部实现了Lock和Condition
CPython中在解释器进程级别有一把锁,叫GIL全局解释锁 GIL保证CPython进程中,只有一个线程执行字节码,甚至在多核CPU中,也是如此
CPython中 IO密集型,由于线程阻塞,就会调度其他线程 CPU密集型,当前线程可能会连续的获得GIL,导致其他线程几乎无法使用CPU IO密集型,使用多线程,CPU密集型,使用多进程,绕开GIL
十五、多进程 由于GIL,多线程未必是CPU密集型程序的好的选择 多进程可以独立的进程环境中运行程序,可以充分利用多处理器 但是进程本身的隔离带来的数据不共享也是一个问题,而且,线程轻量级
1、multiprocessing
Process类
和Thread类的API类似
import multiprocessing
import datetime
def calc(i):
sum = 0
for _ in range(1000000000):
sum += 1
print(i, sum)
if __name__ == "__main__":
start = datetime.datetime.now()
ps = []
for i in range(5):
p = multiprocessing.Process(target=calc, args=(i,), name='cacl={}'.format(i))
ps.append(p)
p.start()
for p in ps:
p.join()
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)
column column pid 进程id exitcode 进程的退出状态码 terminate() 终止指定的进程 2、进程间同步 1、进程间同步提供和线程同步一样的类 2、multiprocessing还提供共享内存,服务器进程来共享数据,还提供了Queue队列,Pipe管道用于进程间通信 3、通信方式不同 1、多进程就是启动多个解释器进程,进程通信必须序列化,反序列化 2、数据的线程安全性问题 由于每个进程中没有实现多线程,GIL可以说没什么用了
3、进程池举例
import multiprocessing
import datetime
def calc(i):
sum = 0
for _ in range(1000000000):
sum += 1
print(i, sum)
if __name__ == "__main__":
start = datetime.datetime.now()
p = multiprocessing.Pool(5)
for i in range(5):
p.apply_async(calc, args=(i,))
p.close()
p.join()
delta = (datetime.datetime.now() - start).total_seconds()
print(delta)
4、多进程、多线程的选择
1、CPU密集型,CPython中使用了GIL,多线程锁相互竞争,多核优势不能发挥,多进程效率更高
2、IO密集型,适合多线程,减少IO序列化开销,且在IO等待的时候,切换到其他线程继续执行,效率不错
十六、concurrent包 concurrent.futures 异步并行任务编程模块,提供一个高级的异步可执行的便利接口 提供了两个池执行器 ThreadPoolExecutor异步调用的线程池的Executor ProcessPollExecutor异步调用的进程池的Executor
1、ThreadPoolExecutor对象
首先需要定义一个池的执行器对象,Executor类子类对象
Future类
方法 含义 ThreadPoolExecutor (max_worker=1) 池中至多创建max_worker个线程的池来同时异步执行,返回Executor实例 submit(fn, *args, **kwargs) 提交执行的函数及其参数,返回Future实例 shutdown(wait=True) 清理池 方法 含义 result( ) 可以查看调用的返回的结果 done( ) 如果调用被成功的取消或执行完成,返回True cancelled 如果调用被成功的取消,返回True running( ) 如果正在运行且不能被取消,返回True cancel( ) 尝试取消调用。如果已经执行,且不能被取消返回False 否则返回True result(timeout=None) 取返回的结果,超时为None,一直等到返回,超时设置 到期,抛出concurrent.futures.TimeoutError异常 exception(timeout =None) 取返回的异常,超时为None,一直等待返回,超时设置到期,抛出concurrent.futures.TimeoutError异常 ThreadPollExecutor例子 import threading import logging import time from concurrent import futures
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(n):
logging.info('begin to work{}'.format(n))
time.sleep(5)
logging.info('finished'.format(n))
# 创建线程池,池的容量为3
executor = futures.ThreadPoolExecutor(max_workers=3)
fs = []
for i in range(3):
f = executor.submit(worker, i) # 返回Future对象,提交工作
fs.append(f)
for i in range(3, 6):
f = executor.submit(worker, i) # 返回future对象
fs.append(f)
while True:
time.sleep(2)
logging.info(threading.enumerate())
flag = True
for f in fs:
logging.info(f.done())
flag = flag and f.done()
if flag:
executor.shutdown()
logging.info(threading.enumerate())
break
ProcessPollExecutor对象
import threading
import logging
import time
from concurrent import futures
FORMAT = '%(asctime)s %(threadName)s %(thread)s %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(n):
logging.info('begin to work{}'.format(n))
time.sleep(5)
logging.info('finished'.format(n))
if __name__ == "__main__":
# 创建线程池,池的容量为3
executor = futures.ProcessPoolExecutor(max_workers=3)
fs = []
for i in range(3):
f = executor.submit(worker, i) # 返回Future对象,提交工作
fs.append(f)
for i in range(3, 6):
f = executor.submit(worker, i) # 返回future对象
fs.append(f)
while True:
time.sleep(2)
logging.info(threading.enumerate())
flag = True
for f in fs:
logging.info(f.done())
flag = flag and f.done()
if flag:
executor.shutdown()
# logging.info(threading.enumerate())
break
支持上下文管理 with ThreadPoolExecutor(max_worker=1) as executor: future = executor.submit(pow,323,1235) print(future.result( )) 总结: 统一了线程池,进程池调用,简化了编程 缺点,无法设置线程名称