首页 > 代码库 > 学习笔记_廖雪峰<Python 2.7 教程>
学习笔记_廖雪峰<Python 2.7 教程>
进程和线程的基础知识
CPU执行代码是顺序执行, 单核CPU通过让任务交替执行, "模拟"除了多任务并发执行. 真正的多任务并发, 是在多核CPU上, 每个CPU负责执行一个任务. 但实际任务数量远多于CPU核心数量, 所以最终还是操作系统把多任务轮流调度到不同的核心上执行.
进程/线程和物理内存(寄存器)/CPU的关联: 函数调用, 会在栈中分配一块空间, 存放局部变量和参数, 调用结束, 栈空间被释放. 每个线程都有独立的栈, 寄存器. 同一进程里的所有线程共享文件, 代码和数据.
进程独立性: 以CPU中的程序计数器PC为例, 物理的PC只有一个, 但是每个进程有独立的逻辑PC, 保存了程序运行中的值, 但CPU轮到该进程时, 就将逻辑PC值复制到物理PC.
Python跨平台支持多进程
Unix/Linux平台Python支持fork(): 父进程复制出一个子进程
Windows平台Python支持Process(target,args): "模拟出fork()的效果", 即父进程所有Python对象pickle后传递给子进程
Python进程间通信的数据交换方式
multiprocessing模块提供的Queue, Pipes
Python多线程基础知识
标准库提供了两个模块: thread和threading(推荐).
任务进程都默认启动一个叫做MainThread的主线程, 主线程可以启动新的子线程.
多线程的变量锁: 高级语言的一条语句在CPU执行时是若干条语句, 计算中的结果会存入临时变量中, 每个线程都有自己的临时变量.
获取锁后一定要释放, 否则等待锁的线程会一直阻塞下去, 成为死线程. Python中可以通过try...finally确保释放.
锁的坏处: (1) 加锁的代码只能以单线程模式执行, 阻止了多线程并发, 降低效率; (2) 可能造成死锁.
Python的GIL锁导致多线程不能充分利用多核CPU
Python的解释器有一个GIL(Global Interpreter Lock)锁, 任何线程执行前, 先获得GIL锁, 每执行100行代码, 解释器会自动释放GIL锁, 让别的线程有机会执行. 所以即使100个线程跑在100核CPU上, 也只能用到1个核.
GIL是Python解释器设计的历史遗留问题, 使得Python不能有效利用多核, 但可以通过多进程实现.
多个Python进程有各自独立的GIL锁, 互不影响.
多线程下选择加锁的全局变量or不得不传递下去的局部变量?
加锁和传递局部变量都是老方法了, 只是局部变量传递起来很麻烦. 如果遇到每个线程一个专属对象, 不能使用全局变量的情况, 作者就想到用一个全局dict, 但是通过线程自身作为key获得保存的对应的对象, 例如:
1 # -*-coding:utf-8 -*- 2 import time, threading 3 4 global_dict = {} 5 6 class Student(object): 7 8 def __init__(self, name): 9 self.name = name 10 11 def return_name(self): 12 return self.name 13 14 15 def do_task_1(): 16 std = Student(‘Elsa‘) 17 global_dict[threading.currentThread()] = std 18 std = global_dict[threading.current_thread()] 19 print ‘thread %s, do_task_1() std.name = %s\n‘ % (threading.currentThread().name, std.return_name()) 20 21 def do_task_2(): 22 std = Student(‘Anna‘) 23 global_dict[threading.currentThread()] = std 24 std = global_dict[threading.current_thread()] 25 print ‘thread %s, do_task_2() std.name = %s\n‘ % (threading.currentThread().name, std.return_name()) 26 27 28 if __name__ == ‘__main__‘: 29 print ‘thread %s is running...‘ % threading.currentThread().name 30 t1 = threading.Thread(target=do_task_1, name=‘t1‘) 31 t2 = threading.Thread(target=do_task_2, name=‘t2‘) 32 t1.start() 33 t2.start() 34 t1.join() 35 t2.join() 36 print ‘thread %s ended.‘ % threading.currentThread().name
global_dict = {}作为一个全局变量, 保存了线程和它的专属对象. 运行结果:
D:\WorkspaceVSCode>python multiprocess.py thread MainThread is running... thread t1, do_task_1() std.name = Elsa thread t2, do_task_2() std.name = Anna thread MainThread ended.
然后Python又简化了对每个线程专属对象的存储和访问, 也就是封装了上面的global_dict = {}. 可以理解成, local_school = threading.local()是一个全局变量, 但它里面保存的是各个线程的局部变量, 还不用管理线程锁了, 感觉很方便的样子.
多线程时使用ThreadLocal用全局变量保存线程的局部变量
常用于为每个线程绑定一个数据库连接, HTTP请求, 用户身份信息等.
进程, 线程和计算密集, IO密集型任务
多进程更稳定, 系统的子进程互相内存独立, 子进程崩溃不会影响其它进程(主进程除外)
多线程更高效, 但由于所有线程共享进程的内存, 一个崩溃会引发系统强制结束整个进程
计算密集型任务, 如视频高清解码, 主要开销在CPU计算, 任务数=核心数最好
IO密集型任务, 如网络, 磁盘IO等, CPU消耗很少, 大部分时间都是在等待IO操作完成, 任务越多, CPU效率越高(有限度)
不能孤立的看待CPU和IO操作间巨大的速度差异
既然一个任务在执行时大部分时间都是CPU等待IO, 单进程单线程并不能充分的利用CPU资源, 在CPU等待IO的时候, 不放用空闲时间去执行CPU计算任务. "现代操作系统对IO操作已经做出了巨大改进, 最大的特点就是支持异步IO. 如果充分利用操作系统提供的异步IO支持, 就可以用单进程单线程模型来执行多任务." (Android里的AsyncTask就是一种异步, 但是它本质是另外起了一个小线程, 所以异步就是多线程?)
学习笔记_廖雪峰<Python 2.7 教程>