多进程和多线程
线程是最小的执行单元,真正的多进程是需要多核CPU支持才行,单核CPU之所以能执行多进程,是因为CPU的执行速度非常之快,在多个进程中交替执行,看起来像是多进程,实际上还是单进程.进程中包含有许多线程,像word之类的编辑文档,既需要记录用户输入的内容,又需要随时保存,打印等服务.
多进程
Python中os模块封装了常见的系统调用,包括fork()方法,可以在Python中创建子进程,但是在Windows下,调用fork() 方法时,却没有…
|
|
Windows上就没有办法编写多进程的程序了嘛?当然不是.
因为Python是跨平台的,multiprocessing模块就是跨平台的多进程模块.
|
|
执行结果:
|
|
创建子进程时,先创建Process实例,传入函数和函数的参数,挑用start方法启动,join方法是让子进程执行完毕后再继续向下执行,通常用于进程间同步.
Pool
如果需要大量的子进程,可以用进程池批量创建子进程:
|
|
执行结果:
|
|
对Pool对象调用join()方法会等待所有的子进程,调用之前必须先调用close()方法,调用close()之后就不能继续添加新的Process了.
子进程
subprocess,可以启动一个子进程,然后控制其输入和输出.
|
|
进程间通信
进程间需要通信,操作系统提供了很多机制实现进程间通信,Python的multiprocessing模块包装了底层的机制,提供了Queue,Pipes等方式交换数据.
|
|
结果有报错:
|
|
明白了错误的原因,在命名的时候queue.py文件名会和要导入的模块有冲突,改成没有冲突的就可以了,比如:qu.py.结果:
|
|
由于Windows没有fork调用,因此,multiprocessing需要“模拟”出fork的效果,父进程所有Python对象都必须通过pickle序列化再传到子进程去,如果multiprocessing在Windows下调用失败了,要先考虑是不是pickle失败了.
实现跨平台多进程,使用multiprocessing模块,进程间通信,用Queu,Pipes.
多线程
多任务可以由多进程来完成,也可以用多线程来完成.一个进程是由多条线程组成的,一个进程至少有一个线程.
Python中提供了两个模块,_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装,一般情况下,用threading.
启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()方法:
|
|
执行结果:
|
|
进程启动都会默认启动一个线程,称之为主线程,主线程的实例叫MainThread,在Python的threading模块中的 current_thread()函数,它永远返回当前线程的实例,子线程的名字在创建时指定,如果没有为子线程命名,Python会自动给线程命名Thread1,Thread2…
Lock
就是锁的意思.多线程和多进程最大的区别,就是在多进程中,同一变量,每个进程都有拷贝一份,互不影响.在多线程中,所有变量都由所有线程共享,任何 一个变量都可以被任何一个线程修改.所以,线程间共享数据最大的危险在于多个线程同时修改一个变量,导致变量内容错误.
|
|
执行结果可能每次都不一样:
|
|
线程的调度是由操作系统决定的,当t1,t2交替执行时候,结果可能会变掉
x为局部变量,当每个线程都有自己的变量x时:
|
|
当t1和t2交替执行时候:
|
|
为了防止变量的内容被多个线程修改错乱,既可以为线程加上锁(Lock),在change_it()方法加上锁,当前线程就获得了锁,其他线程就不能调用change_it()方法,必须等待锁的释放之后,才能对变量进行修改:
|
|
通过threading.Lock()来创建一个锁,加上锁之后,一定要记得通过lock.release()释放锁,否则,会导致线程为死线程.
Python解释器在执行代码时,都会为每个线程加上一个GIL锁(Global Interpreter Lock),线程在执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就会自动释放GIL锁,让其他线程有机会执行,GIL全局锁,实际上把所有线程的执行代码都给加上了锁.所以,多线程在Python中只能交替执行,即使100个线程泡在100核CPU上,也只能用1核.
所以,在Python中,可以使用多线程,但不要期望有效利用多核,在Python中虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务,多个Python进程有各自独立的GIL锁,互不影响.
多线程并发在Python中,不会发生,
ThreadLocal
在多线程环境下,每个线程都有自己的数据,一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看到,不会影响其他线程,而全局变量的修改必须加锁.
当你在函数调用局部变量:
|
|
这样的层级传递很麻烦,使用ThreadLocal时,可以简化这样的调用:
|
|
输出结果:
|
|
使用threading.local()方法来创建一个全局的ThreadLocal对象–>local_school
每个Thread对它都可以读写student属性,互不影响.
可以把local_school看成是全局变量,local_school.student属性都是线程的局部变量,可以任意读写而且互不干扰,也不用管锁的问题,都由ThreadLocal处理.
ThreadLocal最常用的地方是,为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以方便的访问这些资源.
ThreadLocal解决了参数在一个线程中各个函数间互相传递的问题.