返回

python中多进程、多线程概念的进阶探讨

多进程线程的优劣分别是什么?如何开启适当的进程或线程?鲁棒性如何控制

// TODO:
//加入协程相关内容
// 资源安全性
// 进程、线程间通信方式
// 如何捕获子线程异常

简单理解进程和线程

什么是进程

一个任务就是一个进程,是操作系统资源分配的基本单位。
例如打开浏览器,打开word,打开游戏、QQ等等,都是独立的任务,它们各自为一个或者多个进程。
这里要注意的是,同一种任务打开多个,分别属于不同进程,例如chrome打开多个标签,实际上它创建了多个进程。 “运行”的程序才可以称为进程。未运行的程序仅仅是一些指令和数据的集合,并非进程。

什么是线程

线程可以看作一个任务的各项子任务,是操作系统直接的执行单元。
例如播放器,既要解码视频、也要解码音频,所以在进程下存在多线程。在一个进程下一定存在一个线程,可以称它为主线程。

小结

操作系统创建进程时,会单独为每一个进程分配各自的资源,进程与进程之间相互隔离。进程在执行过程中拥有独立的内存单元,而多个线程共享内存。
可见,操作系统执行的粒度是线程,分配资源的粒度是进程,我们的多任务操作系统,在单核CPU上是在各个线程上不断切换而达到目的,而在多核CPU上则多个任务可以创建多个进程来完成,同时也可以创建多个线程来完成,线程是操作系统直接的执行单元。能同时执行多个线程任务。
值得注意的是,在python中,由于全局解释器锁的存在,线程并没有发挥并行计算的作用,而是提供了并发的能力。(只能在一个cpu核上运行)

进程与线程的比较

进程的优缺点

多进程的优点是稳定性好,一个子进程崩溃了,不会影响主进程以及其余进程。
但是缺点是创建进程的代价非常大 ,因为操作系统要给每个进程分配固定的资源,并且,操作系统对进程的总数会有一定的限制,若进程过多,操作系统调度都会存在问题,会造成假死状态。。不过,进程与进程之间是完全隔离的,进程A崩溃了完全不会影响到进程B。

线程的优缺点

多线程优点是效率较高一些,但是致命的缺点是任何一个线程崩溃都可能造成整个进程的崩溃,因为它们共享了进程的内存资源池,没有自己单独的内存地址空间,指针数据的错误可以导致任何同地址空间内其他线程的崩溃,包括进程。

进一步理解进程和线程(可选)

进程

进程的组成

当一个程序被载入内存并成为一个进程后,它会占用一部分存储空间,此空间会分为 4 个区域:

这 4 个区域的作用分别是: 栈(Stack):存储局部变量、函数参数等临时数据。 堆(Heap):进程在执行期间可以动态申请这部分空间。 数据区(Data):存储全局变量和静态变量。 文本区(Text):存储进程要执行的机器指令代码。

进程的生命周期

python中的GIL全局解释器锁

TODO:

检查个人电脑的最大进程、线程数

不同电脑的配置状况决定了一个系统能够运行多少进程以及对应的线程数,简单粗暴的方法是用实例代码来检测到底能运行多少进程和线程:(一般情况下不要过于离谱即可)
不过需要注意的是,不同任务处理所需要占用的内存和cpu使用率是不同的,需要具体情况具体分析,但通常情况下的使用不会出现大问题(除非你考虑到数据共享安全性,想让一组线程执行完后再启动下一轮,那就要根据实际情况设计最大线程并加上线程锁;等到获取到的这些数据处理后才能继续处理下一轮数据;或者使用大小限定队列与线程池) 多进程测试代码 首先,你可以用这个语句(linux与mac)进行直接查看:ubuntu> ps aux | wc -l,如果查询失败或者想要看到更直观的结果可以使用以下代码:

#!/usr/bin/python
 
import os
import sys
import re
import threading
import signal
import time
 
g_exit = 0
num  = 0
 
def sig_process(sig, frame):
	global g_exit
	g_exit = 1
 
def sub_process(data):
	while not g_exit:
		time.sleep(1)
		print data
 
def process():
	num = int(sys.argv[1])
	all_process = []
 
	for i in range(num):
		try:
			pid = os.fork()
		except:
			pid = -1
 
		if pid < 0:
			print 'error in fork'
			all_process.append(-1)
		elif 0 == pid:
			sub_process(i)
			os._exit(0)
		else:
			all_process.append(pid)
 
	while not g_exit:
		time.sleep(100)
 
	for i in range(num):
		if -1 == all_process[i]:
			continue
		os.waitpid(all_process[i], 0)
 
def main():
	if len(sys.argv) != 2:
		print 'wrong number parameter'
		return 0
 
	signal.signal(signal.SIGINT, sig_process)
	process()
 
if __name__ == '__main__':
	main()

多线程测试代码

#!/usr/bin/python
 
import os
import sys
import re
import threading
import signal
import time
 
g_exit = 0
num  = 0
 
def sig_process(sig, frame):
	global g_exit
	g_exit = 1
 
def sub_process(data):
	while not g_exit:
		time.sleep(1)
		print data
 
def process():
	num = int(sys.argv[1])
	all_thread = []
 
	for i in range(num):
		try:
			td = threading.Thread(target = sub_process, args=(i,))
			td.start()
		except:
			all_thread.append(-1)
			continue
 
		all_thread.append(td)
 
	while not g_exit:
		time.sleep(100)
 
	for i in range(num):
		if isinstance(all_thread[i], int):
			continue
		all_thread[i].join()
 
def main():
	if len(sys.argv) != 2:
		print 'wrong number parameter'
		return 0
 
	signal.signal(signal.SIGINT, sig_process)
	process()
 
 
if __name__ == '__main__':
	main()

Reference

Built with Hugo
Theme Stack designed by Jimmy