Python:多进程和多线程

在现实社会,我们经常需要一种场景,就是同时有多个事情需要执行,如在浏览网页的同时需要听音乐。比如说在跳舞的时候要唱歌。
同样的,在程序中我们也可能需要这种场景。如下面我们以同时听音乐和浏览网页为例。

def network():   
  while True:
    print("正在上网~~~")        
    time.sleep(1)def sing():    
  while True:        
    print("正在听歌……")        
    time.sleep(1)
if __name__ == "__main__":    
  network()    
  sing()

执行代码,我们发现代码并没有按照我们的想法执行,而是,一直执行上网的代码,这样就无法完成我们想要的效果。
如果你想要同时听音乐和浏览网页两件事,就需要一个新的方式来完成,这个方式就是----多任务。

多任务是咋回事呢?

什么叫做多任务呢?简单的说,就是操作系统(OS)可以同时运行多个任务。如你可以一边浏览网页,一边听着歌,同时还可以使用画板画着画,这个就是多任务。其实我们的操作系统就是多任务,只是我们都没有注意罢了。
单核CPU中:
1、时间片轮换
2、优先级别调度
3、多核CPU中:
4、并发
5、并行

Fork子进程

编写完的代码,在没有运行的情况下,称之为程序。
正在运行的代码,就成为了进程。
进程,除了包含代码外,还需要运行环境等,所以和程序是存在区别的。
Python在os模块上封装了常用的系统调用,其中就包括了fork,我们可以很轻松地在Python代码中创建进程。

# 注意,下面代码只能在Linux、Mac系统上运行,window下不支持运行!!!
import osimport time# 使用os模块的fork方法可以创建一个新的进程
pid = os.fork()if pid == 0:    
while True:       
  print("son process", pid) # 子进程代码        
  time.sleep(1)
else:    
  while True:        
    print("main process", pid) # 父进程代码        
    time.sleep(1)

Python中的fork() 函数可以获得系统中进程的PID ( Process ID ),返回0则为子进程,否则就是父进程,然后可以据此对运行中的进程进行操作;

但是强大的 fork() 函数在Windows版的Python中是无法使用的。。。只能在Linux系统中使用,比如 Ubuntu 15.04,Windows中获取父进程ID可以用 getpid()。

getpid、getppid

我们可以使用os模块的getpid来获取当前进程的进程编号,同样也可以使用os.getppid()方法案来获取当前进程的父进程编号。

import osimport time# 使用os模块的fork方法可以创建一个新的进程
pid = os.fork()
if pid == 0:    
  while True:        
    print("我是子线程,我的编号是%s,我的父进程编号是%s(os.getpid(),os.getppid()))        
    time.sleep(1)
else:    
  while True:        
    print("我是父线程,我的编号是%s,我的子进程编号是%s" %(os.getpid(), pid))        
    time.sleep(1)
使用os模块中的getpid()方法,可以得到当前线程的编号,使用getppid()方法可以得到该线程的父级编号。

多进程修改全局变量多进程修改全局变量

import os
import time

# 使用os模块的fork方法可以创建一个新的进程
pid = os.fork()

if pid == 0:
    while True:
        print("我是子线程,我的编号是%s,我的父进程编号是%s" %(os.getpid(),os.getppid()))
        time.sleep(1)
else:
    while True:
        print("我是父线程,我的编号是%s,我的子进程编号是%s" %(os.getpid(), pid))
        time.sleep(1)

使用os模块中的getpid()方法,可以得到当前线程的编号,使用getppid()方法可以得到该线程的父级编号。

多进程修改全局变量

import os

# 定义一个全局变量
num = 0

pid = os.fork()

if pid == 0:
    # 子进程
    num += 1
    print("子进程~~~~~~",num)

else:
    #父进程
    num += 1
    print("父进程~~~~~~", num)

输出结果如下

父进程~~~~~~ 1
子进程~~~~~~ 1

由此可见,进程间是无法共享数据的。
注意:多个进程间,每个进程的所有数据(包括全局变量)都是各自拥有一份的,互不影响。

完成最开始的任务

import os
import time

def network():
    for x in range(5):
        print("正在上网~~~")
        time.sleep(1)

def singe():
    for x in range(5):
        print("正在听歌……")
        time.sleep(1)

pid = os.fork()

if pid == 0:
    network()
else:
    singe()

print("程序结束~~~")


多个fork问题

上面的所有程序中,我们使用了fork函数,生成了两个进程(一个主进程、一个子进程),但是如果我们在程序中需要多个进程呢?如两次调用fork函数,会生成三个进程吗?

import os,time

res = os.fork()

if res == 0:
    print("一个子线程")
else:
    print("主线程")

ret = os.fork()
if ret == 0:
    print("第三个线程")
else:
    print("第四个线程")

结果如下

主线程
第四个线程
第三个线程
一个子线程
第三个线程
第四个线程

我们发现,主线程和子线程各执行了一次,但是第三个和第四个都执行了两次,为什么了,看下面的图。


1.png

主进程和子进程的执行顺序

其实通过上面的代码,我们已经发现了,主进程和子进程的执行顺序是没有规律的,这个不受程序员的控制,有操作系统的调度算法来控制。

multiprocessing

前面我们使用os.fork()方法实现了多进程,但是这种方案只能在Linux下运行,window环境下是无法运行的,那么有没有可以实现在任何平台都能运行的多进程了,有!Python为大家提供了multiprocessing模块用来实现多进程。

函数实现方式

from multiprocessing import Process
import os

def run():
    print("这个是一个独立的进程",os.getpid(),os.getppid())

if __name__ == "__main__":
    print("代码开始运行……")
    task = Process(target=run)
    task.start() # 启动进程

    print("代码运行结束……",os.getpid())

结果如下


2.png

我们发现主进程结束后,子进程才结束,说明我们确实开辟了一个独立的进程。

from multiprocessing import Process
import os

def run(msg):
    for x in range(5):
        print("这个是一个独立的进程",os.getpid(),os.getppid())
        print("传递的参数是:",msg)
    else:
        print("子进程结束了……")
if __name__ == "__main__":
    print("代码开始运行……")
    # target表示独立进程的方法
    #args表示要传递的参数,注意:args的类型是元组,也支持列表list
    task = Process(target=run,args=("这个是参数",)) 
    task.start() # 启动进程

    print("代码运行结束……",os.getpid())

args为调用的子进程的函数的参数,注意类型是一个元组。

from multiprocessing import Process
import os

def run(msg):
    for x in range(5):
        print("这个是一个独立的进程",os.getpid(),os.getppid())
        print("传递的参数是:",msg)
    else:
        print("子进程结束了……")
if __name__ == "__main__":
    print("代码开始运行……")
    # target表示独立进程的方法
    #args表示要传递的参数,注意:args的类型是元组
    task1 = Process(target=run,args=("这个是参数1",))
    task1.start() # 启动进程
    task2 = Process(target=run, args=("这个是参数2",))
    task2.start()  # 启动进程
    task3 = Process(target=run, args=("这个是参数3",))
    task3.start()  # 启动进程

    print("代码运行结束……",os.getpid())

多个进程启动还是一样,执行的顺序同样不可控。
(主进程等待子进程版):
join方法表示只有子进程执行完成后,主进程才能结束。主进程会等待子进程完成后,自身才会执行完成。

from multiprocessing import Process
import os,time

def run(msg):
    print("子进程开始执行了……")
    time.sleep(3)
    print("子进程执行end了……")

if __name__ == "__main__":
    print("代码开始运行……")
    task1 = Process(target=run,args=("这个是参数1",))
    print(task1.is_alive()) # is_alive()方法判断进程是否结束
    task1.join() # 表示这个子进程执行完成,主进程才能继续向下执行
    print(task1.is_alive())
    print("代码运行结束……",os.getpid())

常用方法

from multiprocessing import Process
import os,time

def run(msg):
    print("子进程开始执行了……")
    time.sleep(3)
    print("子进程执行end了……")

if __name__ == "__main__":
    print("代码开始运行……")
    # name表示我们认为的为这个子进程取个名称,
    # 如果不写,默认是Process-n n从1开始
    task1 = Process(target=run,args=("这个是参数1",),name="liujianhong")
    task1.start() # 启动进程
    print(task1.is_alive()) # is_alive()方法判断进程是否结束
    task1.join() # 表示这个子进程执行完成,主进程才能继续向下执行
    print(task1.name) # 得到子进程的名称
    task1.terminate()  # 强制结束进程
    print(task1.is_alive())
    print("代码运行结束……",os.getpid())

多个进程使用不同的方法版

from multiprocessing import Process
import os,time

def run(msg):
    print("子进程1开始执行了……")
    time.sleep(3)
    print("子进程1执行end了……")

def run2(msg):
    print("子进程2开始执行了……")
    time.sleep(3)
    print("子进程2执行end了……")

if __name__ == "__main__":
    print("代码开始运行……")
    task1 = Process(target=run,args=("这个是参数1",))
    task1.start() # 启动进程
    task2 = Process(target=run2, args=("这个是参数2",))
    task2.start()  # 启动进程
    print("代码运行结束……",os.getpid())

类实现方式

在Python中,很多的方案都提供了函数和类两种实现方式,如:装饰器、自定义元类。同样多进程也有两种实现,前面我们已经看了使用函数实现的方式,下面我们使用类来实现以下呗。
进程类的实现非常的简单,只要继承了Process类就ok了,重新该类的run方法,run方法里面的代码,就是我们需要的子进程代码。

from multiprocessing import Process
import time

class MyProcess(Process):

    # 重写run方法即可
    def run(self):
        print("一个子进程开始运行了")
        time.sleep(1)
        print("一个子进程开始运行结束了")

if __name__ == '__main__':
    task = MyProcess()
    task.start()
    print("主进程结束了……")

在进程类的实现中如果想要初始化一些前面我们提到过的参数,如进程名称等,可以使用init借助父类来完成。

from multiprocessing import Process
import time

class MyProcess(Process):

    def __init__(self,name):
        super().__init__(name=name)

    # 重写run方法即可
    def run(self):
        print("一个子进程开始运行了")
        time.sleep(1)
        print("一个子进程开始运行结束了")

if __name__ == '__main__':
    task = MyProcess("刘建宏")
    task.start()
    print(task.name)
    print("主进程结束了……")


进程池Pool

当我们需要的进程数量不多的时候,我们可以使用multiprocessing的Process类来创建进程。但是如果我们需要的进程特别多的时候,手动创建工作量太大了,所以Python也为我们提供了Pool(池)的方式来创建大量进程。

from multiprocessing import Pool
import os,time

def run(msg):
    print("开始一个子线程运行了……")
    time.sleep(1)
    print("开始一个子线程运行结束了……")

if __name__ == "__main__":
    pool = Pool(3)  # 表示初始化一个进程池,最大进程数为5
    for x in range(10):
        pool.apply_async(run, args=("hello pool",))
    print("------start----")
    pool.close() # 关闭池
    pool.join() # 等待所有的子进程完成,必须放在close后面
    print("-------end------")

注意:一般我们使用apply_async这个方法,表示非阻塞的运行,一旦使用了apply方法表示阻塞式执行任务,此时就是单任务执行了(一般不会使用,特殊场景才会使用)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容