一、并发和并行的区别
- 并行:同时做某些事,可以互不干扰的同一个时刻做几件事,例如高速公路的车道
- 并发:也是同时做某些事,但是强调同一个时段做了几件事
二、并发的解决
- 食堂中午吃饭,人都涌向食堂,这就是并发,如果人很多,就是高并发
1、队列、缓冲区
- 假设只有一个窗口,陆续涌入食堂的人,排队打菜是比较好的方式
- 排队就是人排成队列,先进先出,解决了资源使用的问题
- 排成的队列其实就是一个缓冲地带,就是缓冲区
2、争抢
- 只开一个窗口,有可能没有秩序,也就是谁挤进去就给谁打饭,挤到窗口的人占据窗口,直到打到饭菜离开
- 其他人继续争抢,会有一个人占据窗口,可以视为锁定窗口,窗口就不能为其他人服务了,这是一种锁机制;抢到资源就上锁,排他性的锁,其他人只能等候
- 争抢也是一种高并发解决方案,但是不好,因为有可能很多人很长时间抢不到
3、预处理
- 如果排队长的原因,是由于每个人打菜等候的时间长,因为要吃的菜没有,需要现做,锁定这窗口,可以提前统计大多数最爱吃的菜品,先做好
- 一种提前加载用户需要的数据的思路,预处理思想,缓冲常用
4、并行
- 成百上千的人同时来吃饭,一个队伍搞不定的,多开打饭窗口形参多个队列,如同多个车道一样,开窗口就得扩大食堂,得多雇人在每个窗口提供服务,造成成本上升
- 日常可以通过购买更多服务器,或多开进程,进程实现并行处理,来解决并发问题
- 如果线程在单个CPU上处理,就不是并行了,但是多数服务器都是多CPU的,服务的部署往往是多机的,分布式的,这都是并行处理
5、提速
- 提高单个窗口的打饭速度,也是解决并发的方式
- 打饭人员提高工作技能,或为单个窗口配备更多的服务人员
- 提高单个CPU性能,或单个服务器安装更多的CPU
- 这是一种垂直扩展思想
6、消息中间件
- 地铁站的九曲回肠的走廊,缓冲人流,进去之后再多口安检进站
- 常见的消息中间件有RabbitMQ,ActiveMQ(Apache),RocketMQ,kafka等
二、进程和线程
- 在实现了线程的操作系统中,线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一个程序的执行实例就是进程
- 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统的结构的基础
- 进程和程序的关系:
- 程序是源代码编译后的文件,而这些文件存放在磁盘上,当程序被操作系统加载到内存中,就是进程,进程中存放着指令和数据,它也是线程的容器
- Linux进程有父进程,子进程,Windows的进程是平等关系
- 线程,有时被称为轻量级进程,是程序执行流的最小单元
- 一个标准的线程由线程ID,当前指令指针,寄存器集合和堆栈组成
1、进程、线程的理解
- 现代操作系统提出进程的概念,每个进程都认为自己独占所有的计算机硬件资源
- 进程就是独立的王国,进程间不可以随便的共享数据
- 线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程拥有自己独立的栈
三、线程的状态
- 就绪(Read):线程能够运行,但在等待被调度,可能线程刚刚创建启动,或刚刚从阻塞中恢复,或被其他线程抢占
- 运行(Runing):线程正在运行
- 阻塞(Blocked):线程等待外部事件发生而无法运行,如I/O操作
- 终止(Terminated):进程完成,或退出,或被取消
四、python的线程开发
1、Thread类
- target:线程调用的对象,就是目标函数
- name: 为线程起个名字
- args :为目标函数传递实参,元组
- kwargs: 为目标函数关键字传参,字典
2、线程启动
举例1: import threading #最简单的线程程序 def worker(): print("I'm working") print('Fineshed') t = threading.Thread(target=worker, name='worker') # 线程对象 t.start() #启动线程 通过threading.Thread创建一个线程对象,target是目标函数,name可以指定名称 通过start()方法调用线程启动 线程之所以执行函数,是因为线程中就是执行代码的,而最简单的封装就是函数, 所有还是函数调用,函数执行完,线程也就退出了 举例2: import threading import time #最简单的线程程序 def worker(): while True: #线程不退出 time.sleep(1) print("I'm working") print('Fineshed') t = threading.Thread(target=worker, name='worker') # 线程对象 t.start() #启动线程 举例3: import threading import time #最简单的线程程序 def worker(): while True: if (count > 5): raise RuntimeError(count) time.sleep(1) print("I'm working") count += 1 t = threading.Thread(target=worker, name='worker') # 线程对象 t.start() #启动线程 print('========End==========') python的线程没有优先级,没有线程组的概念,也不能被摧毁,停止,挂起,那也就没有恢复和中断了
3、线程的传参
举例1: import threading import time def add(x,y): print('{}+{}={}'.format(x,y,x+y, threading.current_thread().ident)) thread1 = threading.Thread(target=add, name='add', args=(4,5)) thread1.start() #启动线程 time.sleep(2) thread2 = threading.Thread(target=add, name='add', args=(5,),kwargs={'y':4}) thread2.start() time.sleep(2) thread3 = threading.Thread(target=add, name='add', kwargs={'x':4, 'y':5}) thread3.start() 线程传参和函数传参没什么区别,本质上就是函数传参
4、threading的属性和方法
- current_thread():返回当前线程对象
- main_thread() : 返回主线程对象
- activ_count() : 当前处于alive状态的线程个数
- enumerate() : 返回所有活着的线程的列表,不包括已经终止的线程和未开始的线程
- get_ident() : 返回当前线程的ID,非0整数
- activ_count,enumerate方法返回的值还包括主线程
举例1: 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'm working") t = threading.Thread(target=worker, name='worker') showthreadinfo() t.start()
5、Thread实例的属性和方法
- name:只是一个名字,只是个标识符,名称可以重名
- ident : 线程ID,它是非0整数,线程启动后才会有ID,否则为None,线程退出,此ID依旧可以,ID可以重复使用, ID必须唯一,但可以在线程退出后再利用
- is_alive(): 返回线程是否活着
举例1: 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(name='worker', target=worker) print(t.ident) t.start()
6、start()和run()方法
- 使用start方法启动线程,是启动了一个新的线程,名字叫做worker运行,但是使用run方法的,并没有启动新的线程,就是在主线程中调用了一个普通的函数而已
- 因此,启动线程使用start()方法才能启动多个线程
五、多线程
- 顾名思义,多个线程,一个进程中如果有多个线程,就是多线程,实现一种并发
举例1: import threading import time def worker(): count = 0 while True: if count > 5: break time.sleep(0.5) count += 1 print('worker runing') 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 = MyThread(name='worker1', target=worker) t2 = MyThread(name='worker2', target=worker) t1.start() t2.start() start方法启动可以看到worker1和worker2交替执行 run方法没有开新的进程,这就是普通函数的调用,所以执行完t1.run(),然后执行t2,这就不是多线程 当使用start方法启动线程后,进程内有多个活动的线程并行的工作,就是多线程 一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程,一个进程至少有一个主线程 其他线程称为工作线程
1、线程安全
举例1: import threading def worker(): for x in range(100): print("{} is runing".format(threading.current_thread().name)) for x in range(1,5): name = "worker{}".format(x) t = threading.Thread(name=name, target=worker) t.start() 上例中,本以为print应该是打印文本之后紧跟着一个换行的,但是有时候确实好几个文本在一起 后面跟上换行,而且发生这种情况的时机不确定,所以,print函数不是线程安全函数 如果是这样,多线程编程的时候,print输出日志,不能保证一个输出一定后面立即换行了 解决print函数换行 1、不让print打印换行 import threading def worker(): for x in range(100): print("{} is runing. ".format(threading.current_thread().name),end='') for x in range(1,5): name = "worker{}".format(x) t = threading.Thread(name=name, target=worker) t.start() 字符串是不可变的类型,它可以作为一个整体不可分割输出,end=''就不在print输出换行了 2、使用logging 标准库里面的logging模块,日志处理模块,线程安全的,生产环境代码都使用logging import threading import logging def worker(): for x in range(100): print("{} is runing. ".format(threading.current_thread().name),end='') 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()
2、daemon线程和non-daemon线程
- 进程靠线程执行代码,至少有一个主线程,其他线程是工作线程
- 主线程是第一个启动的线程
- 父线程:如果线程A中启动了一个线程B,A就是B的父线程
- 子线程:B就是A的子线程
python中,构造线程的时候,可以设置daemon属性,这个属性必须在start方法前设置好
- daemon属性:表示线程是否是daemon线程,这个值必须在start()之前设置,否则引发RuntimeError异常
- isDaemon(): 是否是daemon线程
- setDaemon : 设置为daemon线程,必须在start方法之前设置
举例1: import time import threading def foo(): time.sleep(5) for i in range(20): print(i) #主线程是non-daemon线程 t = threading.Thread(target=foo, daemon=False) t.start() print('Main Thread Exiting') 发现线程t依然执行,主线程已经执行完,但是一直等着线程t 修改为 t= threading.Thread(target=foo, daemon=True),主线程执行完就结束了,根本没有等线程t 总结: 线程具有一个daemon属性,可以显示设置为True或Fale,也可以不设置(默认设置None) 如果不设置daemon,就取当前线程的daemon来设置它 主线程是non-daemon线程,即daemon=False 从主线程创建的所有线程的不设置daemon属性,则默认都是daemon=False,也就是non-daemon线程 python程序在没有活着的non-daemon线程运行时退出,也就是剩下的只能是daemon线程,主线程才能退出,否则主线程只能等待
举例1: import time import threading def bar(): time.sleep(10) print('bar') def foo(): for i in range(20): print(i) t = threading.Thread(target=bar, daemon=False) t.start() #主线程是non-daemon线程 t = threading.Thread(target=foo, daemon=True) t.start() #time.sleep(2) # 让主线程等待2秒后执行,子线程才可以执行到 print('Main Thread Exiting') 上例中,不会输出bar这个字符,因为主线程执行完成后没有到等 举例2: import time import threading def foo(n): for i in range(n): print(i) time.sleep(1) t1 = threading.Thread(target=foo, name='t1', args=(20,), daemon=True) t1.start() t2 = threading.Thread(target=foo, name='t2', args=(10,), daemon=False) t2.start() time.sleep(2) print('Main Thread Exiting') 上例说明,如果有non-daemo线程的时候,主线程退出时,也不会杀掉所有daemon 线程, 直到所有non-daemon线程全部结束,如果还有daemon线程,主线程需要退出,会结束所有daemon线程退出
3、join方法
- join(timeout=None),是线程的标准方法之一
- 一个线程中调用另一个线程的join方法,调用者将被阻塞,直到被调用线程终止
- 一个线程可以被join多次。timeout参数指定调用者等待多久没有设置时,就一直等待被调用者线程结束
- 调用谁的join方法,就是join谁,就要等谁
举例1: 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') 使用了join方法后,daemon线程执行完了,主线程才退出
4、daemon线程应用场景
- 简单来说,本来并没有daemon thread,为了简化程序员工作,让他们不用去记录和管理那些后台线程,创造了一个daemon thread的概念
- 这个概念唯一的作用就是当你把一个线程设置为daemon,它会随主线程的退出而退出
- 主要应用场景有:
- 1、后台任务,如发送心跳包,监控,这种场景最多
- 2、主线程工作才有用的线程,如主线程中维护这公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适
- 3、随时可以被终止的线程;如果主线程退出,想所有其他工作线程一起退出,就使用daemon=true来创建工作线程
举例1: import time import threading def bar(): while True: time.sleep(1) print('bar') def foo(): print("t1 daemon = {}".format(threading.current_thread().isDaemon())) t2 = threading.Thread(target=bar) t2.start() print("t2 daemon = {}".format(t2.isDaemon())) t1 = threading.Thread(target=foo, daemon=True) t1.start() time.sleep(3) print('Main Thread Exiting')
5、threading.loacl类
举例1: import threading import time #局部变量实现 def worker(): x = 0 for i 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是局部变量,能否改造成使用全局变量完成 举例2: import threading import time class A: def __init__(self): self.x = 0 globals_data = A() def worker(): globals_data.x = 0 for i in range(100): time.sleep(0.0001) globals_data.x += 1 print(111111,threading.current_thread(), globals_data.x) for i in range(10): threading.Thread(target=worker).start() 上例虽然使用了全局对象,但是线程之间互相干扰,线程存储的数据可以被其他线程看下,导致了错误的结果 举例3: import threading import time globals_data = threading.local() def worker(): globals_data.x = 0 for i in range(100): time.sleep(0.0001) globals_data.x += 1 print(111111,threading.current_thread(), globals_data.x) for i in range(10): threading.Thread(target=worker).start() python提供threading.local类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象 存储的数据其他线程看不见 上例结果和局部变量的效果一样 举例4: import threading X = 'abc' ctx = threading.local() #注意这个对象所处的线程 ctx.x = 123 print(ctx, type(ctx), ctx.x) def worker(): print(X) print(ctx) print(ctx.x) print('working') worker() #普通函数调用 print() threading.Thread(target=worker).start() #另起一个线程 从运行结果来看,另起一个线程执行ctx.x这句报错了 但是,ctx打印没有出错,说明看到ctx,但是ctx中的x看不到,这个x不能跨线程 threading.local类构建了一个大字典,其元素是每一个线程实例的地址为key和线程对象引用线程单独的字典的映射 通过threading.local实例就可以在不同的线程中,安全地使用线程独有的数据,做到了线程间的数据隔离,如同本地变量一样安全
六、定时器Timer/延迟执行
- threading.Timer继承自Thread,这个类用来定义多久执行一个函数:class threading.Timer(interval,function,args=None,kwargs=None)
- start方法执行之后,Timer对象会处于等待状态,等待了interval之后,开始执行function函数;如果在执行函数之前的等待阶段,使用了cancel方法,就会跳过执行函数结束
举例1: import threading import time import logging FORAMT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(format=FORAMT, level=logging.INFO) def worker(): logging.info('in worker') time.sleep(2) t = threading.Timer(5,worker) t.setName('w1') t.start() # 启动线程 print(111,threading.enumerate()) t.cancel() #取消,可以注释这一句看看如何定时执行 time.sleep(1) print(threading.enumerate()) Timer是线程Thread的子类,就是线程类,具有线程的能力和特征 它的实例是能够延时执行目标函数的线程,在真正执行目标函数之前,都可以cancle它
七、线程同步
- 线程同步是线程间协同,通过某种技术,让一个线程访问某些数据是,其他线程不能访问这些数据,直到该线程完成对数据的操作
- 临界区(Critical Section),互斥量(Mutex),信号量(Semaphore), 事件Event
1、Event
- Event事件是线程间通信机制中最简单的实现,使用一个内部的标记flag,通过flag的True或False的变化来进行操作
- set(): 标记设置为True
- clear() : 标记设置为False
- is_set() : 标记是否为True
- wait(timeout=None) : 设置等待标记为True的时长,None为无限等待,等到返回True,未等到超时了返回False
例如:老板雇佣了一个工人,让他生成杯子,老板一直等着工人,直到生成了10个杯子 举例1: from threading import Event, Thread import logging import time FORAMT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(format=FORAMT, level=logging.INFO) def boss(event:Event): logging.info("I'm boss, waiting for u") #等待 event.wait() logging.info("Good Job") def worker(event:Event, count=10): print(111,event) logging.info("I'm working for U") cups = [] while True: logging.info('make 1') time.sleep(0.5) cups.append(1) if len(cups) >= count: # 通知已完成 event.set() # 标记为True,结束boss等待 break logging.info("I finished my job. cups={}".format(cups)) event = Event() w = Thread(target=worker, args=(event,)) b = Thread(target=boss, args=(event,)) w.start() b.start() 使用同一个Event对象的标记flag,谁wait就是等到flag变为True,或等到超时返回False,不限制等待的个数
2、wait的使用
from threading import Event, Thread import logging import time FORAMT = '%(asctime)s %(threadName)s %(thread)d %(message)s' logging.basicConfig(format=FORAMT, level=logging.INFO) def do(event:Event, interval:int): while not event.wait(interval): #返回True或者False logging.info('do sth') e = Event() Thread(target=do, args=(e, 3)).start() e.wait(10) e.set() print('main exit') wait优于sleep,wait会主动让出时间片,其他线程可以被调度,而sleep会占用时间片不让出
3、Event练习
实现Timer,延时执行的线程,延时计算add(x,y) 思路:Timer的构造函数中参数得有哪些,如何实现start启动一个线程执行函数,如何cancel取消待执行任务 from threading import Event, Thread import logging import time, datetime logging.basicConfig(level=logging.INFO) def add(x:int, y:int): logging.info(x + y) class Timer: def __init__(self, interval, fn, *args, **kwargs): self.interval = interval self.fn = fn self.arge = 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') self.event.wait(self.interval) if not self.event.is_set(): # 标记是否为Ture self.fn(*self.arge, **self.kwargs) delta = (datetime.datetime.now() - start).total_seconds() logging.info('Finished {}'.format(delta)) t = Timer(10,add,4,50) t.start() e = Event() e.wait(4)