多任务

什么叫“多任务”,简单地说,就是操作系统可以同时做多个任务。

单核CPU 要实现多任务,通过调度算法实现,如:时间片轮转、优先级调度等;四核CPU相当于4个单核CPU。

并发: 任务量大于CPU核数,通过操作系统的各种调度算法,实现多个任务“一起”执行(实际上由于切换任务的速度非常快,只是看上去一起执行,并没有真正的同时执行。)

并行: 任务量小于等于CPU核数,级任务是真正的一起执行的。

进程

进程是具有一定独立功能的程序(就是一坨代码,还没有运行时叫程序)关于某个数据集合上的一次运行活动 (是运行的程序),进程是系统进行资源分配的单位。

线程

线程是进程的一个实体,是CPU调度的单位 ,它是比进程更小的能独立运行的基本单位。线程基本上不拥有系统资源,只拥有一点运行中必不可少的资源(如:程序计数器、一组寄存器和栈)。

进程和线程之间的关系: 举个简单的例子:一个手机中运行了好多后台的APP程序,如微信、QQ、支付宝…,其中,一个进程就是运行中的QQ,而QQ中可以跟不同的人进行聊天,每个聊天的窗口就是一个线程,你可以同时跟好多人聊天(即,开好多个聊天窗口,也就是说一个进程中可以由好多线程 ),但是当一个聊天窗口卡死了,QQ就不能运行了(一个线程死掉就等于整个进程死掉 ),只能强制把它关了然后重启,但是你QQ挂了,并不影响你的微信和支付宝的运行(进程有独立的地址空间,一个进程崩溃后,不会对其他进程产生影响 ),同时你可以在不同的聊天窗口发送相同的QQ表情包,但是你不能在微信里发送QQ里的表情包(同进程里的多线程之间共享内存数据,不同进程之间是相互独立的,各有个的内存空间 )。

多进程和多线程

python限定cpu占用数量 python进程数超过cpu核数_python

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--")

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_多进程_02

import os

res = os.fork()

if res == 0:
    print("这是子进程,pid为:%d, 我的父进程是:%d" % (os.getpid(), os.getppid()))
else:
    print("这是父进程,pid为:%d" % os.getpid())

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_python限定cpu占用数量_03

二、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")

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_多任务_04

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)

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_python限定cpu占用数量_05

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")

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_python限定cpu占用数量_06

进程间不能共享参数

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()

运行结果如下:

python限定cpu占用数量 python进程数超过cpu核数_python_07

当同时开启两个进程时,运行结果如下:

python限定cpu占用数量 python进程数超过cpu核数_python_08

进程之间很多情况下是要通信的,在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()

运行结果如下:

python限定cpu占用数量 python进程数超过cpu核数_子进程_09

进程池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")

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_子进程_10

进程池中的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()

运行结果如下:

python限定cpu占用数量 python进程数超过cpu核数_python_11

多进程的简单应用:

多个文件同时读写

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()

运行结果:

python限定cpu占用数量 python进程数超过cpu核数_多任务_12