并发、并行和多线程
序言:并行是解决并发的一种途径
并发:concurrency;同时做某些事,但是强调,一个时段内的所有事情要处理
例如:同一时间段内大量的用户访问处理
并行:paraller;同时做某些事,可以互不干扰的同一时刻做几件事
例如:同一时间有多台服务器一起处理并发的用户访问
并发的解决:
*队列、缓冲区
Queue:解耦、缓冲
*争抢
谁抢到资源就上锁,排他性的锁
优点:争抢也是一种高并发解决方案
缺点:有可能有人很长时间抢不到
*预处理:一种提前加载用户需要的数据的思路
预处理思想,缓存常用
*并行:水平扩展思想;如增加服务器、多开进程、线程实现并行处理
单cpu:线程在单cpu就不是并行;cpu同一时刻只能运行一个线程
*提速:垂直扩展
例如:提高单个CPU性能,或单个服务器安装更多的CPU
*消息中间件:
第三方软件:RabbitMQ、ActiveMQ、RocketMQ、kafka
进程和线程:
线程:在实现了线程的操作系统中,线程是操作系统能够进行运算调度的最小单位;
*线程被包含在进程中,是进程中的实际运作单位
*一个程序的执行实例就是一个进程
进程:计算机中程序关于某数据集合上的一次运行活动;
*是系统进行资源分配和调度的基本单位
*是操作系统结构的基础
进程和程序的关系:
*程序是源代码编译后的文件,而这些文件存放在磁盘上。
*当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据,它也是线程的容器
*程序是指令和数据的集合;而进程是程序运行在内存中的一个实例
线程和进程:
进程:
1,每一个进程都认为自己独占所有的计算机硬件资源;
2,一个进程无法直接访问另一个进程的变量和数据结构
3,一个进程可以有多个线程
线程:
1,同一个进程内的多个线程会共享部分状态
2,多个线程可以读写同一块内存(进程不能)
3,每个线程拥有自己独立的寄存器和栈
*进程切换开销大,线程切换开销小;进程间通信开销大,线程间通信开销小;
*线程属于进程,不能独立执行。每个进程至少要有一个线程,成为主线程
线程的状态:
就绪(Ready):线程能够运行,但在等待被调度。
*可能线程刚刚创建启动,或刚刚从阻塞状态恢复,或者被其他线程抢占
运行(Running):线程正在运行
阻塞(Blocked):线程等待外部事件发生而无法运行,如I/O操作
终止(Terminated):线程完成,或退出,或被取消
Python中的进程和线程:
进程会启动一个解释器进程,线程共享一个解释器进程;
线程模块:threading
threading的属性和方法:
current_thread():返回当前线程对象
main_thread():返回主线程对象
active_count():当前处于alive活动状态的线程个数(包含主线程)
enumerate():返回所有活动状态的线程列表,不包括已经终止的线程和未开始的线程(包含主线程)
get_ident():返回当前线程的ID,非0整数
Thread类:
创建一个线程对象:
例如:threading.Thread(target=add, name='s1', args=(4, 5))
target:线程调用的对象,就是需要执行的函数
name:为线程起个名字
args:为目标函数传递实参,是一个元组
kwargs:为目标函数关键字传参,是一个字典
Thread实例的属性和方法:
name:一个标识,可以重名;getName()、setName()获取、设置这个名字
ident:线程ID,是非0整数。线程启动后才会有ID,否则为None。线程退出,此ID依旧可以访问,此ID可以重复使用
*ID必须唯一,但可以在线程退出后再分配给其他线程使用
is_alive():返回线程是否活着
start():启动线程;每一个线程必须且只能执行该方法一次,有个开关量is_set()
*start()会调用run()
1,先执行 _start_new_thread(self._bootstrap, ())
2,self._bootstrap调用self._bootstrap_inner()
3,self._bootstrap_inner()中执行self.run()
*同一个线程对象只能start一次,第二会抛出运行时异常
解释:在创建一个线程对象时,有一个 self._started = Event()
1,Event()这个实例有一个实例属性self._flag = False
2,当start执行启动一个新的线程: _start_new_thread(self._bootstrap, ())
3,实例的_bootstrap方法执行实例的_bootstrap_inner()
4,在实例的_bootstrap_inner()中执行了self._started.set()中的self._flag = True
5,当前实例的self._started.is_set()属性值是True保存在这个实例对象中
6,下一次调用这个实例start方法,会判断self._started.is_set()为真就抛出raise异常!
if self._started.is_set():
raise RuntimeError("threads can only be started once")
run():run()方法可以运行函数
run()和start()区别:
run():运行在当前主线程上,没有创建新的线程
start():先创建一个新的线程,在调用run()运行函数在这个新的线程上压栈
join方法:使用了join方法后,daemon线程执行完,主线程才退出
join(timeout=None):是线程的标准方法之一
作用:一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止或执行完。
*一个线程可以被join多次
*timeout参数指定调用者等待多久,没有设置超时,就一致等到被调用线程结束
*调用谁的join方法,就是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('---------')
local类:threading.local类构建了一个大字典;
{id(Thread) ---> (ref(Thread), thread-local dict)}
*key:每一个线程实例的地址
*value:vlaue是一个元组;元组的第一个元素是线程对象,第二个元素是每个线程自己的字典
示例代码:
import threading
import time
global_data = threading.local()
def worker():
global_data.x = 100
for i in range(100):
time.sleep(0.01)
global_data.x += 1
print(threading.current_thread(), global_data.x)
for i in range(10):
threading.Thread(target=worker).start()
Timer类(定时器):延迟执行
格式:def __init__(self, interval, function, args=None, kwargs=None):
*threading.Timer继承自Thread,这个类用来定义多久执行一个函数;
*执行start()方法之后,Timer对象会处于等待状态,等待了interval之后,开始执行function函数
*如果在执行函数之前的等待阶段,使用了cancel方法,就会跳过执行函数结束。
示例代码:
import threading
import logging
import time
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)
th = threading.Timer(5, worker)
th.setName('thname')
th.start() # 启动线程
print(threading.enumerate())
th.cancel() # 取消,必须在函数执行之前
time.sleep(1)
print(threading.enumerate())
Timer类总结:
*Timer是线程Thread的子类,具有线程的能力和特征!
*Timer类的实例能够延时执行目标函数的线程,在真正执行目标函数之前,都可以cancel()
***Lock类:锁
作用:凡是存在共享资源争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源
方法:
acquire(blocking=True, timeout=-1):默认为阻塞,阻塞可以设置超时时间。
*非阻塞时,timeout禁止设置;成功获得锁,返回True,否则返回False
release():释放锁,可以从任何线程调用释放
*已经上锁的锁,会被重置为unlocked状态
*未上锁的锁上调用,抛出RuntimeError异常
锁的应用场景:
*少用锁,必要时用锁,使用了锁,多线程访问被锁的资源时,就成了串行,排队执行或者争抢执行
*加锁时间越短越号,不需要立即释放锁
*一定要避免死锁
问题:一般,加锁就需要解锁,但是加锁后解锁前,还要执行一些代码,就有可能抛出异常,一旦出现异常,锁时无法释放,但是当前线程可能因为这个异常被终止了,这样就产生了死锁
加锁、解锁常用语句:
1,使用try...finally语句保证锁的释放
2,with上下文管理,锁对象支持上下文管理
示例代码:
import threading
from threading import Thread, Lock, Event
import time
class Counter:
def __init__(self):
self._val = 0
self.__lock = Lock()
@property
def value(self):
with self.__lock:
return self._val
def inc(self):
try:
self.__lock.acquire()
self._val += 1
finally:
self.__lock.release()
def dec(self):
with self.__lock:
self._val -= 1
def run(c, 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):
Thread(target=run, args=(c, c2)).start()
while True:
time.sleep(1)
if threading.active_count() == 1:
print(threading.enumerate())
print(c.value)
break
问题:10个工人一起生成1000个杯子
存在的问题:多生产杯子。当前线程还没有提交杯子,就被切换到另外一个线程了。
示例代码:
from threading import Thread, Lock, current_thread
import logging
import time
FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
cups = [] # 全局变量,共享资源
def worker(count=10):
logging.info("I'm working for U.")
while len(cups) < count:
time.sleep(1)
cups.append(1)
logging.info('I finished. cups={}'.format(len(cups)))
for _ in range(10):
Thread(target=worker, args=(100,)).start()
使用Lock锁:
import threading
from threading import Thread, Lock
import logging
import time
FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
cups = []
lock = Lock()
def worker(count=10):
logging.info("I'm working for U.")
flag = False
while True:
lock.acquire() # 获取锁
if len(cups) >= count:
flag = True
time.sleep(1)
if not flag:
cups.append(1)
lock.release()
if flag:
break
logging.info('I finished. cups={}'.format(len(cups)))
for _ in range(10):
Thread(target=worker, args=(10,)).start()
***可重入锁RLock:
*可重入锁,是线程相关的锁
*可重入锁,可在一个线程中获取锁,并可以继续在同一线程中不阻塞获取锁;当锁未释放完,其他线程获取锁就会阻塞,直到当前持有锁的线程释放完锁。
Condition类:
作用:Condition用于生产者,消费者模型,为了解决生产者消费者速度匹配问题。
构造方法Condition(lock=None),可以传入一个Lock或RLock对象;默认是RLock
acquire(*args):获取锁
wait(self, timeout=None):等待或超时
notify(n=1):唤醒至多指定数目个数的等待的线程,没有等待的线程就没有任何操作
notify_all():唤醒所有等待的线程
问题:生产者和消费者速度匹配问题?
示例代码:
from threading import Thread, Event, Condition
import logging
import random
FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Dispather:
def __init__(self):
self.data = None
self.event = Event()
self.cond = 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("receive {}".format(self.data))
self.data = None
self.event.wait(0.5)
d = Dispather()
p = Thread(target=d.produce, args=(10,), name='produce')
c = Thread(target=d.consume, name='consume')
c.start()
p.start()
Condition总结:
机制:
*采用了通知机制,非常有效率
*消费者wait(),等待通知
*生产者生产完成,对消费者发送通知,可以使用notify或者notify_all方法
作用:
*可以用于生产者和消费者模型中,解决生产者消费者速度匹配的问题
使用方式:
使用Condition,必须先acquire(加锁),用完要release(释放锁),因为内部使用了锁,默认使用RLock锁
*使用with上下文
线程启动:对线程对象执行start()方法
例如:threading.Thread(target=add, name='s1', args=(4, 5)).start()
*通过threading.Thread创建一个线程对象,target是目标函数,name可以指定名称;现在只是创建一个线程对象,这个对象并没有真正在内存中运行
*调用start方法,就可以让线程运行
重点:线程之所以执行函数,是因为线程中就是执行代码的,而最简单的封装就是函数,所以还是函数调用。函数执行完,线程也就退出了
Python中的线程退出:
Python没有提供线程退出的方法:
*线程函数内语句执行完毕,没有需要处理的,正常退出
*线程函数中抛出未处理的异常
*Python中的线程没有优先级、没有线程组的概念,也不能被销毁、停止、挂起、中断、恢复
多线程:一个进程中有多个线程,就是多线程,实现一种并发!
*当使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程
*一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程,一个进程至少有一个主线程,其他线程成为工作线程。
问题:因为print函数是线程不安全的。
示例代码:
import threading
import time
def worke():
count = 0
while True:
if count > 5:
break
time.sleep(3)
count += 1
print('worker running')
print(threading.current_thread().name, threading.current_thread().ident)
print('+++++++++++')
class MyThread(threading.Thread):
def start(self):
print('start-----------')
super().start()
def run(self):
print('run-------------')
super().run()
t1 = MyThread(name='worker1', target=worke)
t2 = MyThread(name='worker2', target=worke)
if __name__ == '__main__':
t1.start()
t2.start()
线程安全:
问题:print函数被打断了,被线程切换打断了。
1,print函数分为两步,第一步打印字符串
2,第二步打印换行,就在这之间,发生了线程的切换,换行还没有打印之前,切换到了其他线程
线程安全:
线程执行一段代码,不会产生不确定的结果,这段代码就是线程安全的。
***使用logging模块:见模块篇***
daemon线程和non—daemon线程:
主线程是第一个启动的线程;
父线程:如果线程A中启动了一个线程B,A就是B的父线程
子线程:B就是A的子线程
daemon:表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常
isDaemon():是否是daemon线程
setDaemon:设置为daemon线程,必须在start()方法之前设置完成
应用场景:
1,后台任务;如发送心跳包、监控
2,主线程工作才有用的线程,如主线程中维护着公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适
3,随时可以被终止的线程
*构造线程时,可以设置daemon属性,这个属性必须在start方法前设置完成。
def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
if daemon is not None:
self._daemonic = daemon
else:
self._daemonic = current_thread().daemon
*如果未设置daemon属性,默认为None,取当前线程的daemon值
Fasle:non-daemon线程;如果有non-daemon线程,主线程退出时,也不会杀掉所有daemon线程,直到所有non-daemon线程全部结束
True:daemon线程;如果有daemon线程时,主线程需要退出时,会结束所有daemon线程,结束退出。
示例代码:
import threading
import time
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()
time.sleep(2)
print("Main Thread Exiting")
线程同步:
线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作。
***Event类:
Event事件,是线程间通信机制中最简单的实现,使用一个内部的标记flag,通过flag为True或False的变化来进行操作。
set():标记设置为True
clear():标记清除,设置为False
is_set():标记是否为True
wait(timeout=None):设置等待标记为True的时长,None为无限等待;等到返回True,未等到超时了返回False
问题:一个老板,一个员工,员工需要生产100个杯子
示例代码:
from threading import Event, Thread
import logging
import time
FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
def worker(event, count=10):
time.sleep(1)
logging.info('------this start------')
cups = []
while True:
logging.info('make1')
time.sleep(0.5)
cups.append(1)
if len(cups) >= count:
event.set()
break
logging.info('finished. cups={}'.format(cups))
def boss(event):
logging.info('start-------')
event.wait()
logging.info('stop--------')
event = Event()
w = Thread(target=worker, args=(event,))
b = Thread(target=boss, args=(event,))
w.start()
b.start()
总结:
*使用同一个Event()实例对象的标记flag
*谁wait就是等到flag标记变为True,或等到超时时返回False
*同一个Event()实例对象的wait()可以被多个使用。
wait():默认为空,永久等待;如果给值就等待这个值
优点:Event的wait()优于time.sleep(),它会更快的切换到其他线程,提高并发效率
示例代码:
from threading import Event, Thread
import logging
logging.basicConfig(level=logging.INFO)
def do(event, interval):
while not event.wait(interval):
logging.info('do something.')
event = Event()
Thread(target=do, args=(event, 3)).start()
event.wait(10)
event.set()
print('main exit')
问题:自己实现Timer类
示例代码:
from threading import Event, Thread
import datetime
import logging
FORMAT = '%(asctime)s %(threadName)s %(thread)d %(message)s'
logging.basicConfig(format=FORMAT, level=logging.INFO)
class Timer:
def __init__(self, interval, func, *args, **kwargs):
self.interval = interval
self.func = func
self.args = args
self.kwargs = kwargs
self.event = Event()
def start(self):
Thread(target=self.run).start()
def cancel(self):
self.event.set()
def run(self):
start = datetime.datetime.now()
logging.info('waiting')
if not self.event.wait(self.interval):
self.func(*self.args, **self.kwargs)
data = (datetime.datetime.now() - start).total_seconds()
logging.info('finished {}'.format(data))
self.event.set()