2. threading.local的作用? import threading # 创建全局ThreadLocal对象: localVal = threading.local() localVal.val = "Main-Thread" def process_student(): print( '%s (in %s)' % (localVal.val, threading.current_thread().name)) def process_thread(name): #赋值 localVal.val = name process_student() t1 = threading.Thread(target= process_thread, args=('One',), name='Thread-A') t2 = threading.Thread(target= process_thread, args=('Two',), name='Thread-B') t1.start() t2.start() t1.join() t2.join() print(localVal.val) #打印结果: ''' One (in Thread-A) Two (in Thread-B) Main-Thread ''' ''' threading。local()这个方法:用来保存一个全局变量,但是这个变量只能被当前线程访问 localVal.val = name这条语句可以储存一个变量到当前线程,如果在另外一个线程里面再次 对localVal.val进行赋值,那么会在另外一个线程单独创建内存空间来存储,也就是说在不同 的线程里面赋值 不会覆盖之前的值,因为每个线程里面都有一个单独的空间来保存这个数据, 而且这个数据是隔离的,其他线程无法访问。本质上是在不同的线程在使用这个方法的时候为其 创建了一个独立的内存空间! ''' ''' 用处; 多线程下载,实现同时下载多个歌曲,用这个方法来保存每个下载线程的数据 ''' 3. 进程之间如何进行通信? 可以利用queue,joinablequeue,pipe来实现 7、多道技术 最初:操作系统将硬盘中的应用程序一个个读到内存,操作系统将内存中的应用程序交给cpu执行 ,遇到io就等待。 多道技术:将硬盘上的多个数据全部读到内存中,操作系统为其划分不同的内存空间,此时不同内存空间内的应用程序,内存空间,操作系统对其的调度称为进程。 空间复用:由于将不同的程序放到不同的内存空间,实现了空间复用 时间复用:一个程序遇到io阻塞,操作系统会切换另外一个进程,交给cpu去执行 ,实现了时间复用 8、分时系统 即使当前在cpu里运行的程序没有遇到io,操作系统会在一定的时间后自动切换为另外一个进程 ,这样的切换其实没有提高cpu的效率,牺牲了一些效率,但是实现了多个程序共同执行的效果。 9、进程的概念 进程 是指应用程序,内存空间,操作系统的调度 称为一个进程。 竞争计算机系统有限资源的基本单位,进行处理机调度的基本单位。 10、并发与并行 并发:单个cpu下+多道技术就可以实现并发, 并行:同时运行,多核cpu才能实现并行 11、同步 异步 同步:提交任务后等待任务的返回结果,导致下一个任务只能在上一个任务结束后才能运行 异步:,任务的提交互不影响 12、阻塞 非阻塞 阻塞:遇到io操作 等待是阻塞的副作用 非阻塞:没有遇到io操作 13、同步阻塞 同步非阻塞 异步阻塞 异步非阻塞 同步阻塞:相当于一个线程在等待 同步非阻塞:相当于一个线程在正常运行 异步阻塞:多个线程都在等待 异步非阻塞:多个线程都在正常运行。 14、io 指的是读入和写出数据的过程,和等待读入/写出数据的过程,一旦拿到数据后就变成数据 操作了,就不是io了。即一个是等待数据的过程,一个是读写拷贝数据的过程。 15、阻塞io 非阻塞io 阻塞io:用户线程被阻塞在等待数据或拷贝数据上 非阻塞io:用户线程没有因为io的事情出现阻塞,即数据已经拷贝好后,采取通知用户线程,一上来将就可以直接操作数据 16、同步io 同步阻塞io 同步io指的是发起io请求后,必须拿到io的数据才能继续执行。 同步阻塞io: 在等待数据和拷贝数据过程中,线程都在阻塞,这就是同步阻塞io; 在等待数据的过程中,线程采用死循环式轮询,在拷贝数据的过程,线程在阻塞! 在io上,同步和非阻塞是互斥的,所以不存在同步非阻塞io. 所以,同步io一定是阻塞io,同步io也就是同步阻塞io. 17、异步io 和 异步阻塞/非阻塞io 异步io 发起io请求后,不用拿到io数据就可以继续执行。 异步阻塞io:等待数据的过程,用户线程继续执行,拷贝数据的过程 ,线程在阻塞。 异步非阻塞io: 等待和拷贝的过程中,用户线程都在继续执行。 本质上是因为用户线程既没有参与 等待,也没用参与拷贝的过程,所以是异步的。当其接到通知时,数据已经准备好了!没有因为io而阻塞,所以是非阻塞的。 18、进程的创建: 1)、系统初始化(前台进程负责与用户交互,后台 运行的进程与用户无关,运行在后台且需要时才被唤醒的进程,称为守护进程,如电子邮件,web页面) 2)、一个进程在运行过程中开启了子进程,如nginx开启多进程 3)、用户的交互式请求,如双击暴风影音 4)、一个批处理作业的初始化 19、进程的结束; 正常退出 出错退出 严重错误 try...except捕获异常 被其他进程杀死 20、进程的同步部分 进程之间数据不共享,但是共享同一套文件系统,同一个打印终端,共享带来的就是竞争,竞争带来的就是数据紊乱,可以加锁处理 例如:开启多进程打印时,终端显示的数据有先有后,可以选择加锁让数据排列的整齐一些。 21、进程的数据共享,进程间的数据通信 1)、基于queue队列拉实现 from multiprocessing import Queue q = Queue(3) q.put(3) q.put(3) q.put(3) #q.put(4) 再放一个,队列已满,程序会停在这,直到数据被取走 try: q.put_nowait(3) # 使用这种 格式,队列满了不会阻塞,但是会直接报错 except: print('队列满了') # 因此可以在取数据之前查看队列的状态 print(q.full()) # 满了就True,否则为False print(q.get()) print(q.get()) print(q.get()) # print(q.get()) # 和put类似,队列空了的话,再取会阻塞 try: q.get_nowait(3) # 可以使用这种格式,队列空了不会阻塞,但是会直接报错 except: print('队列已空') #关于q.empty() # 在空队列上放置q.empty时,可能会返回True,因为存在无限小的延迟。 # 基于队列生产者消费者模型,在队列空了以后,程序会阻塞,进程不会结束。消费者在取空队列后,一直处于死循环中的等待数据被生产! 2) JoinableQueue from multiprocessing import JoinableQueue, Process import random, time def producer(name, food, q): for i in range(5): data = '%s拉出了%s,%s' % (name, food, i) time.sleep(random.randint(1, 3)) print(data) q.put(data) # 将数据放入队列中 def consumer(name, q): while True: data = q.get() time.sleep(random.randint(1, 3)) print('%s消化了%s' % (name, data)) q.task_done() # 告诉队列,已经将数据取出并消化完毕 if __name__ == '__main__': q = JoinableQueue() # 生产一个队列对象 a1 = Process(target=producer, args=('tank', '披萨', q)) a2 = Process(target=producer, args=('egon', '面包', q)) b1 = Process(target=consumer, args=('owen', q)) b2 = Process(target=consumer, args=('fuck', q)) a1.start() a2.start() b1.daemon = True b2.daemon = True b1.start() b2.start() # 等待生产者生产完所有的数据 a1.join() a2.join() # 等待队列里的数据全部取出 q.join() print('主进程') ''' q.join():生产者调用该方法进行阻塞,直到队列里的所有数据全部被拿走, 阻塞将持续到队列里每一个项目均调用q.task_done()方法为止,也就是队列里的所有数据都被get取走了 ''' 3)、管道 from multiprocessing import Process,Pipe def f(conn): conn.send('hello boy') # 子进程发送的消息,任何数据类型都可以 conn.close() if __name__ == '__main__': parent_conn,child_conn = Pipe() # 建立管道,拿到管道的两端,双方都可以收发数据 p = Process(target=f,args=(child_conn,)) p.start() print(parent_conn.recv()) # 主进程接收的消息 p.join() ''' 管道通信不安全: 返回的两个通信对象,每个对象都有recv和send方法。 如果两个进程或线程在同一端读取或写入数据,那么管道中的数据可能会损坏! ''' 22、信号量 Semaphore 23、event事件 24、全局解释器锁GIL 这是 针对cpython而言的,为了保证在同一时间内只能存在一个线程去执行。我程序员控制的同步数据是针对于可见的变量,而GIL同步的是解释器后台爱的不可见变量,例如为了进行垃圾回而维护的引用计数,如果没有GIL,那么可能出现线程切换导致对同一个对象释放两次的情况!GIL是解释器相关的,而不是python这种语言的特性! 存在的意义: 相当于执行权限,保护着线程之间共享的数据; 拿到gil后才能拿到互斥锁,其他线程也可以拿到gil,但是发现互斥锁没有被释放的话,gil权限要立刻交出来! 25、互斥锁 用来实现对共享资源的同步访问。 import threading r = reading.Lock() r.acquire() ''' 对公共数据的操作部分 ''' r.release() 26、死锁 两个或者两个以上的进程或线程在执行过程中,抢夺资源而造成的一种互相等待的现象,若无外力干预下,将一直保持这种状态! 解决;采用递归锁 27、递归锁 from threading import Rlock Rlock内部维持着一个Lock和counter变量,后者记录acquire的次数,从而可以使得资源被多次require, 直到一个线程所有的acquire都被release,其他线程才能获得资源 28、线程之间的通信 线程队列 ''' import queue 队列 q = queue.Queue(3) # 里面的参数可以不传 q.put(1) q.put(2) q.put(3) try: q.put_nowait(4) except: print('队列已满') print(q.get()) print(q.get()) print(q.get()) try: print(q.get_nowait()) except: print('队列已空') # 结果 为先进先出 ''' ''' import queue q = queue.LifoQueue() # 类似于栈 q.put(1) q.put(2) q.put(3) #q.put_nowait() print(q.get()) print(q.get()) print(q.get()) # print(q.get_nowait()) #结果:先进后出 ''' ''' import queue q = queue.PriorityQueue() # 排序 q.put((10,'a')) q.put((11,'b')) q.put((12,'a')) print(q.get()) print(q.get()) print(q.get()) #对于优先级,元组里的第一个元素通常是数字,也可以是非数字之间去比较大小 #比较的结果中,该元素越小,优先级越高 ''' 29、线程池 进程池 concurrent.futures模块提供了高度封装的 异步调用接口 from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor pool = ThreadPoolExecutor() pool.submit(fn,*args,**kwargs).add_done_callback(fn) 小点: map (func,*iterables,timeout=None) 取代佛如循环submit的操作 add_done_callback(fn):回调函数 result;回调函数拿到的形参.result 可以拿到上一个函数的返回值。 shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作 wait = True 等待池里所有的任务执行完毕回收完资源后才继续 wait = False 立即返回,不等待池子里的所有线程结束。 但是不管wait为何值,整个程序都会等所有任务执行完毕。 submit和map需在sutdown之前。 30、协程 本质上是线程,之前线程任务的切换是由操作系统来完成的,遇到io就切换,现在我们自己可以利用协程实现 较操作系统来切换更少的开销。主要是用在 开关线程,创建寄存器,堆栈等,自己用程序去控制任务的切换! 协程是一个用户态的轻量级的线程,线程由用户程序区控制调度。 1)、关于生成器的知识点瑕疵复习的时候再去研究!! 2)、yield实现协程 - 任务的切换与保存 import time def consumer(): while True: x = yield time.sleep(1) print('接收了数据{}'.format(x)) def producer(): g = consumer() next(g) # 需要先得到初始化一次的生成器 for i in range(10000): g.send(i) print('发送了数据{}'.format(i)) start = time.time() producer() end = time.time() print(end-start) ''' 实际上利用yield仅仅只是实现了切换与保存状态,即并发的效果 并没有节省i/o时间,没有提高效率 ''' 3)、Greenlet 对于上者,可以利用该模块去实现,不过同样遇到io就原地阻塞。没有实现遇到io就自动切换的功能 4)、协程所在的库 Gevent Gevent是一个第三方库,可以通过其实现并发同步和 异步编程。 4.1)简单用法: import gevent def eat(name): print('%s eat 1'%name) gevent.sleep(2) print('%s eat 2'%name) def play(name): print('%s play 1'%name) gevent.sleep(1) print('%s play 2'%name) if __name__ == '__main__': g1 = gevent.spawn(eat,'michael') g2 = gevent.spawn(play,'tank') gevent.joinall([g1,g2]) print('主线程') # 结果: ''' michael eat 1 tank play 1 tank play 2 michael eat 2 主线程 ''' ''' 之所以用到 gevent.sleep() 是因为gevent可以识别这种类型的阻塞 而time.sleep()或者其他类型的 阻塞是不能直接识别的,必须加上补丁 ''' 4.2)、gevent之同步异步: from gevent import spawn, joinall, monkey monkey.patch_all() import time def task(pid): time.sleep(1) print('task %s done' % pid) def synchronous(): # 同步 for i in range(10): task(i) def asynchronous(): # 异步的 g_l = [spawn(task, i) for i in range(10,20)] joinall(g_l) if __name__ == '__main__': print('synchronous:') synchronous() print('asynchronous:') asynchronous() ''' 上面程序先是执行synchronous,然后在执行asynchronous 后者内部是协程,遇到阻塞通过协程实现自动切换,从而达到单线程下实现并发的效果 '''