wordpress主页定制/seo排名优化软件价格
这几天由于遇到了python中的多线程和多进程的问题,所以查了查资料,随便尝试了一下。
首要问题就是,什么是线程,而什么又是进程?
在此贴出一位大佬的知乎答案,我觉得说的很好,就不再重复说了,答案在这,https://www.zhihu.com/search?type=content&q=%E7%BA%BF%E7%A8%8B%E4%B8%8E%E8%BF%9B%E7%A8%8B
总结而言,就是两者都是对一种客观现象的描述。进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文,而线程是进程的更小划分,线程共享了进程的上下文环境,可以共享资源。
提到python,也不得不提一下GIL,python之中的GIL的存在是为了多线程的安全问题。譬如多线程之中,对于同一个数据,线程1进行计算,而计算中间需要进行一下I/O处理,当线程去进行I/O处理的时候,需要释放CPU,如果没有GIL,则会导致python的垃圾回收线程获取解释器的执行权,从而将线程1的数据进行回收,使得线程1的计算白白浪费。而GIL本身可以看做是一把锁,相当于cpython解释器的权限,每一个进程都有一个独立的GIL,进程之间互不影响。而线程之中,谁能抢到GIL,谁就能获得解释器的权限,就能执行代码。
在python中,先贴出结论,然后再分析原因。

对于CPU单核而言,可以多线程,也可以多进程。但是单核情况下的进程也是并发的,很容易理解,由于计算单元只有一个,所以进程在某一特定时刻,只能有一个进程。由于进程之间的创建很浪费时间(可以认为是保存程序的上下文需要费时费力),切换了也还是一个cpu进行计算,不如让它耐心的一个个算好了。而同属于一个进程的多个线程,切换之间并不耗时,但是当遇到I/O处理的时候可以将cpu的算力让给其他需要的线程,所以对于单核而言,不管是计算密集型还是I/O密集型,都可以选择开启一个进程,多个线程。
在python之中,但对于多核而言,由于算力不止一个了,对于计算密集型的多任务,多进程可以完全是并行的,而多线程由于GIL的限制,同一时刻只能有一个GIL被抢占,其他的只能等待交替执行,这样反而会浪费时间。但I/O密集型,CPU算力有也白白浪费,创建那么多线程,也没有用上,还浪费创建时间,不如单进程多线程,这样线程在I/O操作的时候,也会将算力让给别的线程。
写代码进行试验:
假设任务为计算型的,如:

分别利用普通的单进程和多进程相比较:

比较的结果为:

如果是I/O密集型操作



分别用10个线程和10个进程对I/O密集型任务进行测试,有:

从上面看出,在处理I/O密集型多任务的时候,多进程的处理时间甚至比多线程要慢。不难理解,由于多进程的创建本身比较耗时,其在多核的并行处理优势在I/O任务并不能起到太大用处,所以其耗时反而要比多线程要大。
那么,python之中的多进程怎么写呢?python的多进程的引用都来自一个叫multiprocessing的包,这个包中有一个叫Process的类,这个类和Thread类的使用方法是很相似的。比如创建方法:同样可以写入Process(target, args)等参数创建一个进程。和线程的创建方法一样。Start()开启线程/进程,jon()阻塞线程/进程等。
但是process为了方便,引入了进程池的概念,也就是Pool类,Pool可以提供指定数量的进程供用户使用,默认是CPU的个数,(因为进程之间独占GIL,所以如果进程数大于CPU的数目,就不能实现完全的并行了)当有新的请求提交到Pool中的时候,如果Pool没有满,将会自动创建一个进程来执行任务,否则就会阻塞等待。
其中apply_async和apply的区别:
两者都会对进程进行添加,然后自动调用start()开始执行进程,但前者有池子的概念,表示如果池子没有满,可以直接添加并运行,如果池子满了,则可以等里面的运行完成有空位的之后再进入。而后者则相当于阻塞运行,也就是一个运行完了,我池子才让后面的进来。

所以每个循环都apply_async,都是往pool里面添加进程,不管pool是否满了。

而apply是阻塞式运行,所以相当于我不执行完,谁都不准动的赶脚。

既然进程之间有各自的GIL,也就是有着各自的资源和内存,那么他们如何通信呢?可以通过Queue和Pipe进行通信,两者有点相同,都是一边放一边取的意思,queue,也就是数据结构里面的队列,用put放,用get取。而pipe是管道,是管道就有两端,如果一端send,另一端就可以recv进行接收。
下面对两种方法进行举例:

吃包子进程吃到None,就退出进程,主程序结束。

con1管道口发送hello,receiver,而con2.recv可以接收该数据。
最后再贴出一个如何用锁实现进程的同步问题。

其实也不难,就是在进程创建的时候,将一个全局锁当做为参数传入进程,这样通过acquire和lock对锁进行获取和解锁,这样就可以CPU,直到计算完成。
上面的执行结果将会是,先不不停的加上1,然后在加上3.如果不加锁的话,将会导致,时而加1,时而加3,有的时候还会导致num混用,总之,结果不可预测。