多任务
什么叫“多任务”,简单地说,就是操作系统可以同时做多个任务。
单核CPU 要实现多任务,通过调度算法实现,如:时间片轮转、优先级调度等;四核CPU相当于4个单核CPU。
并发: 任务量大于CPU核数,通过操作系统的各种调度算法,实现多个任务“一起”执行(实际上由于切换任务的速度非常快,只是看上去一起执行,并没有真正的同时执行。)
并行: 任务量小于等于CPU核数,级任务是真正的一起执行的。
进程
进程是具有一定独立功能的程序(就是一坨代码,还没有运行时叫程序)关于某个数据集合上的一次运行活动 (是运行的程序),进程是系统进行资源分配的单位。
线程
线程是进程的一个实体,是CPU调度的单位 ,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点运行中必不可少的资源(如:程序计数器、一组寄存器和栈)。
进程和线程之间的关系: 举个简单的例子:一个手机中运行了好多后台的APP程序,如微信、QQ、支付宝…,其中,一个进程就是运行中的QQ,而QQ中可以跟不同的人进行聊天,每个聊天的窗口就是一个线程,你可以同时跟好多人聊天(即,开好多个聊天窗口,也就是说一个进程中可以由好多线程 ),但是当一个聊天窗口卡死了,QQ就不能运行了(一个线程死掉就等于整个进程死掉 ),只能强制把它关了然后重启,但是你QQ挂了,并不影响你的微信和支付宝的运行(进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响 ),同时你可以在不同的聊天窗口发送相同的QQ表情包,但是你不能在微信里发送QQ里的表情包(同进程里的多线程之间共享内存数据,不同进程之间是相互独立的,各有个的内存空间 )。
多进程和多线程
python中的多进程
- 1、fork的使用
- Unix/Linux操作系统提供了一个fork()系统调用,它非常特殊,fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后,分别在父进程和子进程内返回。
- 子进程永远返回0,而父进程返回子进程的ID。这样做的理由是,一个父进程可以fork出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID。
- Python的os模块封装了常见的系统调用,其中就包括fork,可以在Python程序中轻松创建子进程:
- 2、multiprocessing库的使用
- 如果你打算编写多进程的服务程序,Unix/Linux无疑是正确的选择。但是在Windows中没有fork调用
- 由于Python是跨平台的,自然也应该提供一个跨平台的多进程支持。multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了一个Process类来代表一个进程对象
一、fork函数的使用
import os
os.fork()
print("--110--")
运行结果:
import os
res = os.fork()
if res == 0:
print("这是子进程,pid为:%d, 我的父进程是:%d" % (os.getpid(), os.getppid()))
else:
print("这是父进程,pid为:%d" % os.getpid())
运行结果:
二、Multiprocessing库的使用
- Process语法结构如下:
- Process([group [, target [, name [, args [, kwargs]]]]])
- target:如果传递了函数的引用,可以任务这个子进程就执行这里的代码
- args:给target指定的函数传递的参数,以元组的方式传递
- kwargs:给target指定的函数传递命名参数
- name:给进程设定一个名字,可以不设定
- group:指定进程组,大多数情况下用不到
- Process创建的实例对象的常用方法:
- start():启动子进程实例(创建子进程)
- is_alive():判断进程子进程是否还在活着
- join([timeout]):是否等待子进程执行结束,或等待多少秒
- terminate():不管任务是否完成,立即终止子进程
- Process创建的实例对象的常用属性:
- name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
- pid:当前进程的pid(进程号)
from multiprocessing import Process
import os
def process_run(name):
print("Run child process %s (%s)" % (name, os.getpid()))
if __name__ == '__main__':
# 主进程从这里开始执行
print("Run parent process %s" % os.getpid())
# 创建Process实例
p = Process(target=process_run, args=("laozhao",))
print("Child process will start")
# 启动子进程p
p.start()
# 等待子进程结束才能往下执行
p.join()
print("Child process end")
运行结果:
join()函数,主进程等待子进程运行结束后,才能继续往下执行
from multiprocessing import Process
import os
import time
def process_run():
# print("Child process --1")
# time.sleep(0.5)
while True:
print("Child process --1")
time.sleep(0.5)
if __name__ == '__main__':
print("Parent process --start")
p = Process(target=process_run)
p.start()
p.join() # 主进程运行到这要等到子进程运行结束后才能继续往下执行,子进程里是一个while死循环,所以永远执行不到下面
while True:
print("Parent process --2")
time.sleep(1)
运行结果:
terminate()函数,强制结束子进程的运行
子进程传递参数,可以传递不定长参数、关键字参数
from multiprocessing import Process
import os
import time
def process_run(name, *args, **kwargs):
print("Child process --1")
print("Child process running, name={}, args={}, kwargs={}".format(name, args, kwargs))
for i in range(10):
print(i)
time.sleep(0.5)
print("Child process end")
if __name__ == '__main__':
print("Parent process --2")
# 给子进程指定函数传递参数
p = Process(target=process_run, args=("laozhao", "帅哥", 18), kwargs={"addr":"USA"}, name="Child-1")
p.start() # 开启子进程
print("Child process's name is %s" % p.name)
time.sleep(4)
# 等待子进程结束后才能往下执行
# p.join()
# 不管子进程有没结束,强行终止子进程
p.terminate()
print("Chile process --1 has forced termination")
运行结果:
进程间不能共享参数
from multiprocessing import Process
import os
import time
# 定义全局变量
nums = [1, 2, 3]
def process_run_1():
print("Child process -1, pid={}, nums={}".format(os.getpid(), nums))
for i in "abc":
nums.append(i)
time.sleep(1)
print(" Child process -1, pid={}, nums={}".format(os.getpid(), nums))
def process_run_2():
print("Child process -2, pid={}, nums={}".format(os.getpid(), nums))
for i in range(4):
nums.append(i)
time.sleep(1)
print(" Child process -2, pid={}, nums={}".format(os.getpid(), nums))
if __name__ == "__main__":
print("Parent process")
p1 = Process(target=process_run_1)
p2 = Process(target=process_run_2)
print("Start child process -1")
p1.start()
p1.join()
print("End child process -1 and start process -2")
p2.start()
运行结果如下:
当同时开启两个进程时,运行结果如下:
进程之间很多情况下是要通信的,在Multiprocessing模块中提供了Queue实现多进程之间的通信。
from multiprocessing import Queue
q = Queue(3) # 初始化一个Queue对象,最多可以接受3条put消息
q.put("Message 1")
q.put("Message 2")
print(q.full()) # 判断消息队列是否满了,返回True or False
q.put("Message 3")
print(q.full())
try:
q.put("Message 4", True, 2) # 当消息队列已满后,等待2秒后会抛出异常
except:
print("The queue is full, the number of current queue is {}".format(q.qsize()))
try:
q.put_nowait("Message 5")
except:
print("The queue is full, the number of current queue is {}".format(q.qsize()))
# 推荐使用下面方式进行消息队列的写入
if not q.full():
q.put_nowait("Message xx")
# 读取队列消息时,先判断消息队列是否为空,然后再读取
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
False
True
The queue is full, the number of current queue is 3
The queue is full, the number of current queue is 3
Message 1
Message 2
Message 3
说明
- 初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
- Queue.qsize():返回当前队列包含的消息数量;
- Queue.empty():如果队列为空,返回True,反之False ;
- Queue.full():如果队列满了,返回True,反之False;
- Queue.get([block[, timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True;
- 1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出"Queue.Empty"异常;
- 2)如果block值为False,消息列队如果为空,则会立刻抛出"Queue.Empty"异常;
- Queue.get_nowait():相当Queue.get(False);
- Queue.put(item,[block[, timeout]]):将item消息写入队列,block默认值为True;
- 1)如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出"Queue.Full"异常;
- 2)如果block值为False,消息列队如果没有空间可写入,则会立刻抛出"Queue.Full"异常;
- Queue.put_nowait(item):相当Queue.put(item, False);
小Demo:Queue实现多进程之间的信息通信,父进程创建两个子进程,一个写数据,一个读数据
from multiprocessing import Process, Queue
import time, random
def process_run_1(q):
for i in ["I love python!", "abc", 123]:
if not q.full():
print('Put {} to queue...'.format(i))
q.put_nowait(i)
time.sleep(random.random())
else:
print("The queue is full")
def process_run_2(q):
while True:
if not q.empty():
value = q.get(True) # 如果消息队列为空,会一直堵塞在这里
print('Get %s from queue.' % value)
time.sleep(random.random())
else:
break
if __name__ == "__main__":
q = Queue()
p_write = Process(target=process_run_1, args=(q, ))
p_read = Process(target=process_run_2, args=(q, ))
p_write,start()
p_read.start()
运行结果如下:
进程池pool
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。
- multiprocessing.Pool常用函数解析:
- apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
- close():关闭Pool,使其不再接受新的任务;
- terminate():不管任务是否完成,立即终止;
- join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;
from multiprocessing import Pool
import os, time
def process_run_1(num):
print("Child process {} start, the number of this process is {}".format(num, os.getpid()))
time.sleep(0.5)
print("Child process {} end.")
if __name__ == "__main__":
pool = Pool(5) # 定义一个进程池,最大进程数5
for i in range(1, 11):
pool.apply_async(process_run_1, (i, )) # 使用非阻塞方式调用func
print("Start child process")
pool.close() # 关闭进程池,不再接受新的请求
pool.join() # 等到pool中的所有子进程执行接受,必须放在close之后
print("End")
运行结果:
进程池中的queue,如果使用Pool创建子进程,则需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue()。
from multiprocessing import Pool, Manager
import os, time, random
def process_read(queue):
print("Child process_read {} start, the parent process is {}".format(os.getpid(), os.getppid()))
while True:
if not queue.empty():
print("Get message from Queue:{}".format(queue.get(True)))
else: # 如果queue是空,有可能是还没写完,等一秒再判断一下,如果还是这样,那我就退出了
time.sleep(1)
if queue.empty():
break
def process_write(queue):
print("Child process_write {} start, the parent process is {}".format(os.getpid(), os.getppid()))
for i in ["I love python", 123, True, 3.5, "laozhao"]:
if not queue.full():
queue.put_nowait(i)
time.sleep(random.random())
else:
print("The queue is full")
if __name__ == "__main__":
queue = Manager().Queue()
pool = Pool()
pool.apply_async(process_write, (queue, ))
pool.apply_async(process_read, (queue, ))
pool.close()
pool.join()
运行结果如下:
多进程的简单应用:
多个文件同时读写
import os
import multiprocessing
import time, random
def copy_file(queue, file_name, source_folder_name, dest_folder_name):
f_read = open(source_folder_name + "/" + file_name, 'rb')
f_write = open(dest_folder_name + "/" + file_name, 'wb')
while True:
content = f_read.read(1024)
if content:
f_write.write(content)
else:
break
f_read.close()
f_write.close()
queue.put(file_name)
def main():
source_folder_name = input("Enter the file name you want to copy:") # 源文件夹名
dest_folder_name = source_folder_name + "[Copy]" # copy后的文件夹名
if not os.path.exists(dest_folder_name): # 判断该文件夹是否存在,如果不存在就创建它
os.makedir(dest_folder_name)
file_names = os.listdir(source_folder_name) # 源文件夹中文件名(列表)
pool = multiprocessing.Pool()
queue = multiprocessing.Manager().Queue()
for file_name in file_names: # 有一个文件就创建一个进程
pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))
pool.close()
all_file_num = len(file_names)
while True: # 显示进度
file_name = queue.get()
if file_name in file_names:
file_names.remove(file_name)
copy_rate = (all_file_num-len(file_names)) * 100 / all_file_num
print("\r%.2f...(%s)" % (copy_rate, file_name) + " " * 50, end="")
if copy_rate >= 100:
break
if __name__ == "__main__":
main()
运行结果: