一、多进程
-
什么是进程
- 计算机程序只不过是磁盘中可执行的、 二进制(或其它类型)的数据
- 进程(有时被称为重量级进程)是程序的一次执行
- 每个进程都有自己的地址空间、 内存以及其它记录其运行轨迹的辅助数据
- 操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间
-
什么是forking
- fork(分岔)在Linux系统中使用非常广泛
- 当某一命令执行时,父进程(当前进程)fork出一个子进程
- 父进程将自身资源拷贝一份,命令在子进程中运行时,就具有和父进程完全一样的运行环境
-
进程的生命周期
- 父进程fork出子进程并挂起
- 子进程运行完毕后,释放大部分资源并通知父进程,这个时候,子进程被称作僵尸进程
- 父进程获知子进程结束,子进程所有资源释放
-
僵尸进程
- 僵尸进程没有任何可执行代码,也不能被调度
- 如果系统中存在过多的僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程
- 对于系统管理员来说,可以试图杀死其父进程或重启系统来消除僵尸进程
-
forking编程
- 需要使用os模块
- os.fork()函数实现forking功能
- python中,绝大多数的函数只返回一次,os.fork将返回两次
- 对fork()的调用,针对父进程返回子进程的PID;对于子进程,返回的PID为0
-
解决zombie问题
- 父进程通过os.wait()来得到子进程是否终止的信息
- 在子进程终止和父进程调用wait()之间的这段时间,子进程被称为zombie(僵尸)进程
- 如果子进程还没有终止,父进程先退出了,那么子进程会持续工作。 系统自动将子进程的父进程设置为init进程,init将来负责清理僵尸进程
- python可以使用waitpid()来处理子进程
- waitpid(-1,1)接受两个参数,第一个参数设置为:-1,表示与wait()函数相同;第二参数如果设置为0表示挂起父进程,程序不向下执行,直到子程序退出,设置为1表示不挂起父进程,程序一直往下执行
- waitpid()的返回值:如果子进程正在运行、尚未结束则返回0,否则返回子进程的PID
示例:waitpid()处理zombie子进程
import os
import time
pid = os.fork()
if pid:
print('in parent, sleeping...')
time.sleep(10)
print(os.waitpid(-1, 1)) # 处理子进程,如果子进程存在且是僵尸进程,释放它
# 如果不是,父进程什么也不做,继续向下执行
time.sleep(20)
else:
print('in child, sleeping...')
time.sleep(15)
二、多线程
-
什么是线程
- 线程(有时被称为轻量级进程)跟进程有些相似。 不同的是,所有的线程运行在同一个进程中,共享相同的运行环境
- 线程有开始,顺序执行和结束三部分
- 线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步
- 一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯
- 线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能
- 需要注意的是,在单CPU 的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一小会,然后就把CPU 让出来,让其它的线程去
-
多线程的动机
- 在多线程(MT)编程出现之前,电脑程序的运行由一个执行序列组成,执行序列按顺序在主机的中央处理器(CPU)中运行
- 无论是任务本身要求顺序执行还是整个程序是由多个子任务组成,程序都是按这种方式执行的
- 即使子任务相互独立,互相无关(即,一个子任务的结果不影响其它子任务的结果)时也是这样
- 如果并行运行这些相互独立的子任务可以大幅度地提升整个任务的效率
-
多线程任务的工作特点
- 它们本质上就是异步的,需要有多个并发事务
- 各个事务的运行顺序可以是不确定的,随机的,不可预测的
- 这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标
- 根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果
-
多线程编程相关模块
- thread和threading模块允许程序员创建和管理线程
- thread模块提供了基本的线程和锁的支持,而threading提供了更高级别、 功能更强的线程管理功能
- 推荐使用更高级别的threading模块
- 只建议那些有经验的专家在想访问线程的底层结构的时候,才使用thread模块传递函数给Thread类
- 多线程编程有多种方法,传递函数给threading模块的Thread类是介绍的第一种方法
- Thread对象使用start()方法开始线程的执行,使用 join() 方法挂起程序,直到线程结束
示例:
import time
import os
import threading
def calc():
result = 0
for i in range(50000000):
result += i
print(result)
if __name__ == '__main__':
# start = time.time()
# calc()
# calc()
# end = time.time()
# print(end - start)
################################
# start = time.time()
# pid = os.fork()
# if not pid:
# calc()
# exit()
# pid = os.fork()
# if not pid:
# calc()
# exit()
# os.waitpid(-1, 0) # 处理子进程,挂起父进程
# os.waitpid(-1, 0)
# end = time.time()
# print(end - start)
#################################
start = time.time()
t1 = threading.Thread(target=calc)
t1.start()
t1.join() # 挂起主线程,等到t1线程结束后,再向下执行
t2 = threading.Thread(target=calc)
t2.start()
t2.join()
end = time.time()
print(end - start)
-
传递可调用类给Thread类
- 传递可调用类给Thread类是介绍的第二种方法
- 相对于一个或几个函数来说,由于类对象里可以使用类的强大的功能,可以保存更多的信息,这种方法更为灵活
示例:
import threading
import subprocess
class Ping:
def __init__(self, host):
self.host = host
def __call__(self):
result = subprocess.call(
'ping -c2 %s &> /dev/null' % self.host,
shell=True
)
if result == 0: # result的值就是ping命令的退出码,即$?
print('%s:up' % self.host)
else:
print('%s:down' % self.host)
if __name__ == '__main__':
ips = ['172.40.58.%s' % i for i in range(1, 255)]
for ip in ips:
t = threading.Thread(target=Ping(ip))
t.start() # Ping(ip)() <==> target()