Python 多进程和多线程 的使用

Posted IT_Holmes

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 多进程和多线程 的使用相关的知识,希望对你有一定的参考价值。

1. Python 多进程 创建使用


程序本身开始就会是一个主进程,主进程里默认就会有一个主线程。主进程中可以创建多个子进程。

多进程的创建使用三个步骤:

import time

# 1.导包
import multiprocessing

def dance():
    for i in range(5):
        # 故意给他推迟1秒
        time.sleep(1)
        print("跳舞",i)

def sing():
    for i in range(5):
        time.sleep(1)
        print("唱歌",i)


if __name__ == '__main__':
    # 整个py文件就是一个进程(主进程),可以在它的下面创建多个子进程。

    # 2.创建子进程
    # Process的参数:
    # target:指定执行的任务名(函数名)
    my_dance = multiprocessing.Process(target=dance)
    my_sing = multiprocessing.Process(target=sing)

    # 3. 开启子进程(如果不开启是不会执行子进程的)
    my_sing.start()
    my_dance.start()

很简单的三步,导包(import),创建子进程(Process),开启子进程(start)。这就是一个简单的多进程案例。

2. 进程的编号 id


获取进程编号的目的是验证主进程和子进程的关系,可以得知子进程是由那个主进程创建出来的。

获取进程编码的两种操作:

  • os.getpid() 获取当前进程编号。
  • os.getppid() 获取当前父进程编号。
import time
import multiprocessing
import os

def dance():
    # 获取子进程id,因为当前函数就是子进程
    print('dance子进程id:',os.getpid())
    # 获取dance子进程的父进程
    print('dance的父进程:',os.getppid())

    for i in range(5):
        time.sleep(1)
        print("跳舞",i)

def sing():
    # 获取子进程id
    print('sing子进程id:',os.getpid())
    # 获取sing子进程的父进程
    print('sing的父进程:',os.getppid())

    for i in range(5):
        time.sleep(1)
        print("唱歌",i)


if __name__ == '__main__':

    # 获取当前主进程的id编号
    print('主进程id:',os.getpid())

    my_dance = multiprocessing.Process(target=dance)
    my_sing = multiprocessing.Process(target=sing)


    my_sing.start()
    my_dance.start()

3. Process name 参数(进程名)

我们通过name参数定义进程名。
这里只需要记住怎么获取进程名即可:

print(multiprocessing.current_process())

import multiprocessing

def dance():
    # 获取进程名
    print(multiprocessing.current_process())
    
    for i in range(5):
        print("跳舞")

if __name__ == '__main__':

    my_dance = multiprocessing.Process(target=dance,name='老王')
    my_dance.start()

4. 多进程的参数 args 和 kwargs


args:元组(单个元素的元组有 , 号)

kwargs:字典的形式(key值要和函数中的形参完全一致)

import multiprocessing

def dance(a,b):
    for i in range(5):
        print("跳舞",i)
    a+=b
    print(a)

def sing(张三,李四):
    for i in range(5):
        print(张三,李四)



if __name__ == '__main__':
    # 带有参数的函数
    # args:元组(单个元素的元组有 , 号)
    my_dance = multiprocessing.Process(target=dance,args=(5,6,))

    # kwargs:字典的形式(key值要和函数中的形参完全一致)
    # 注意这里字典的key值必须是字符串类型(不可变类型!!)
    my_sing = multiprocessing.Process(target=sing,kwargs={"张三":1,"李四":2,})

    my_dance.start()
    my_sing.start()

5. 进程之间不共享全局变量


进程是分配资源的最小单位 ==》每一个进程(主进程,子进程)都会有自己的独立空间。

创建子进程会对主进程进行拷贝。

因此总体上,进程之间是不共享全局变量的!


import multiprocessing
import time
# 定义一个全局变量列表
g_num = []

def my_write():

    global g_num
    # 向全局变量g_num里写入数据
    for i in range(5):
        # 向g_num添加数据
        g_num.append(i)

    print("my_write:",g_num)

def my_read():

    global g_num
    print("my_read:",g_num)


if __name__ == '__main__':


    my_wri = multiprocessing.Process(target=my_write)
    my_rea = multiprocessing.Process(target=my_read)

    my_wri.start()

    # 先让上面写入全局完成后在读取
    time.sleep(1)

    my_rea .start()

# my_write: [0, 1, 2, 3, 4]
# my_read: []
# 发现进程之间是不共享全局变量的!!!

6. 守护进程


在系统默认主进程和子进程中,如果主进程先执行完毕,那么它会等待子进程结束之后,再结束!!!

现在我们想要主进程退出子进程销毁,不让主进程再等待子进程去执行。

  • 第一种方式,设置守护主进程方式:子进程对象.daemon = True
  • 第二种方式,销毁子进程方式:子进程对象.terminate()
import multiprocessing
import time


def func():
    for i in range(5):
        time.sleep(0.2)
        print("func")

if __name__ == '__main__':

    # 默认:主进程会等待子进程结束之后,再结束!!!

    # 程序一旦执行,就默认创建主进程。
    my_func = multiprocessing.Process(target=func)

    # 方式1:设置守护主进程(进程),注意:这个设置必须start()方法之前设置。
    # my_func.daemon = True
    my_func.start()

    time.sleep(0.5)

    # 方式2:手动设置,terminate()方法结束子进程。
    my_func.terminate()
    print("主进程结束")
    exit() # 参数0:无错误退出,参数1:有错误退出

    # exit()退出程序,主进程结束,子进程也结束

7. 多线程


7.1 线程 介绍


在Python中,想要实现多任务除了使用进程,还可以使用线程来完成,线程是实现多任务的另外一种方式。

一个程序启动,就会有一个主进程,这个主进程中就包含有一个主线程。

多线程执行效果图:

7.2 多线程 实现方式


线程是使用资源的最小单位 ==> 依附于进程,也就是先有了进程才有了线程,它俩是从属关系。


多线程的使用:

  1. 导包: import threading
  2. 创建子线程:threading.Thread(一系列参数)
  3. 开启子线程:xxx.start()
# 1. 导包
import threading
import time

def dance():
    for i in range(5):
        print("跳舞",i)
        time.sleep(1)


def sing():
    for i in range(5):
        print("唱歌",i)
        time.sleep(1)

if __name__ == '__main__':
    # 2.创建子线程
    my_dance = threading.Thread(target=dance)
    my_sing = threading.Thread(target=sing)

    # 3. 开启子线程
    my_sing.start()
    my_dance.start()

多线程 threading.Thread()的参数:

和多进程的参数一样,args和kwargs,一个元组,一个字典。

注意:字典的key值要与函数的形参完全重名才能赋予。

import threading

def dance(count):
    for i in range(count):
        print("跳舞",i)

if __name__ == '__main__':
    # args参数元组:
    # my_dance = threading.Thread(target=dance,args=(5,))

    # kwargs参数元组:
    my_dance = threading.Thread(target=dance, kwargs={"count":5})

    my_dance.start()

上面代码中,一个进程两个线程(1个主进程,1个主线程,1个子线程),一定区分好进程和线程个数。


线程注意点:

  • 线程之间执行是无序的。
  • 主线程会等待所有的子线程执行结束再结束。
  • 线程之间共享全局变量。
  • 线程之间共享全局变量数据出现错误问题。

8. 线程的无序执行

所谓的无序执行,就是当线程进入阻塞状态时,都想使用cpu是无序的。

import threading
import time


def func():
    time.sleep(2)
    print(threading.current_thread())

    # <Thread(Thread-3, started 7640)> 第一次执行的是thread3
    # <Thread(Thread-2, started 6076)> 第二次执行的是thread2
    # <Thread(Thread-1, started 6700)> 总之是无序运行的 ...
    # <Thread(Thread-5, started 2756)>
    # <Thread(Thread-4, started 588)>

if __name__ == '__main__':
    # 这里的for循环仅仅是创建了5个子线程
    for i in range(5):
        # 循环my_func是被重新赋值的
        my_func = threading.Thread(target=func)
        my_func.start()
        

9. 守护线程


同样,主线程会等待所有子线程执行结束后再结束。

import threading
import time


def func():
    for i in range(5):
        time.sleep(0.2)
        print("func")

if __name__ == '__main__':
    my_func = threading.Thread(target=func)
    my_func.start()

    time.sleep(0.5)
    print("主线程已经执行结束正在等待子线程结束。")

守护主线程:
守护主进程就是主线程退出子线程要被销毁不再执行。

  • 方式一:threading.Thread(target=show_info,daemon=True)
  • 方式二:线程对象.setDaemon(True)
import threading
import time


def func():
    for i in range(5):
        time.sleep(0.2)
        print("func")

if __name__ == '__main__':
    # 方式一:Thread(daemon=True)
    # my_func = threading.Thread(target=func,daemon=True)

    my_func = threading.Thread(target=func)

    # 方式二:
    my_func.setDaemon(True)

    my_func.start()

    time.sleep(0.5)
    print("主线程已经执行结束正在等待子线程结束。")

# func
# func
# 主线程已经执行结束正在等待子线程结束。

10. 线程之间 共享全局变量


由于线程是依附于进程的,一个进程中的所有的线程都是使用的同一片内存空间。
也就是说一个进程中,线程是共享该进程的全局变量。

import threading
import time

g_num = []

def my_write():
    global g_num
    for i in range(5):
        g_num.append(i)
    print("write:",g_num)

def my_read():
    global g_num
    print("read:",g_num)

if __name__ == '__main__':
    sub_write = threading.Thread(target=my_write)
    sub_read = threading.Thread(target=my_read)

    sub_write.start()
    time.sleep(1)
    sub_read.start()

# 线程之间共享全局变量
# write: [0, 1, 2, 3, 4]
# read: [0, 1, 2, 3, 4]

11. 线程共享全局变量 出现的问题


如果两个线程同时操作全局变量,可能会出现非常严重的错误如下:

import threading

g_num = 0

def sum_num1():
    global g_num
    for i in range(1000000):
        g_num+=1
    print("sum_num1:",g_num)

def sum_num2():
    global g_num
    for i in range(1000000):
        g_num+=1
    print("sum_num2:",g_num)

if __name__ == '__main__':
    sub_num1 = threading.Thread(target=sum_num1)
    sub_num2 = threading.Thread(target=sum_num2)

    sub_num1.start()
    sub_num2.start()

# sum_num1: 1201969
# sum_num2: 1394111
# 期望的结果是10000000和20000000,但实际却不是!

为什么出现这种问题??
其实本质上,就是竞争cpu资源(由于CPU调度机制造成的)的事情,其中一方使用,另一方可能就会被搁置。


为了解决上面的问题就是用了一下内容:

  • 使用线程同步,xxx.join()来解决。
  • 也可以使用互斥锁解决。

12. 线程同步


线程同步:一个任务执行完成以后代码再继续执行,让其执行第二个线程。
使用join()可以达到线程同步的效果。

import threading

g_num = 0

def sum_num1():
    global g_num
    for i in range(1000000):
        g_num+=1
    print("sum_num1:",g_num)

def sum_num2():
    global g_num
    for i in range(1000000):
        g_num+=1
    print("sum_num2:",g_num)

if __name__ == '__main__':
    sub_num1 = threading.Thread(target=sum_num1)
    sub_num2 = threading.Thread(target=sum_num2)

    sub_num1.start()

    # 线程同步:一个任务执行完成以后代码再继续执行,让其执行第二个线程。
    # sub_num1.join()作用:让主线程等待sub_num1执行完毕后,再往下执行。
    sub_num1.join()

    sub_num2.start()

# sum_num1: 1000000
# sum_num2: 2000000

13. 互斥锁


互斥锁:对共享数据进行锁定,保证同一时刻只能有一个线程去操作。

互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其他等待的线程再去抢这个锁。


互斥锁的使用步骤:(三个步骤)

import threading

g_num = 0

# 1. 创建互斥锁(我们要操作下面的两个函数,因此要定义全局锁)
mutex = threading.Lock() # mutex就是一把互斥锁

def sum_num1():

    # 2. 上锁
    mutex.acquire()

    global g_num
    for i in range(1000000):
        g_num+=1
    print("sum_num1:",g_num)

    # 3. 解锁(如果不解锁,就会产生死锁问题)
    mutex.release()

def sum_num2():

    # 2. 上锁(如果同一把锁,必须先解锁才可以上锁)
    mutex.acquire()

    # 两个函数执行时,一个占用资源被调用上锁,另一个函数无法上锁,cpu不会给它使用的资源,只能阻塞!!
    global g_num
    for i in range(1000000):
        g_num+=1
    print("sum_num2:",g_num)

    # 3. 解锁(如果不解锁,就会产生死锁问题)
    mutex.release()

if __name__ == '__main__':
    sub_num1 = threading.Thread(target=sum_num1)
    sub_num2 = threading.Thread(target=sum_num2)

    sub_num1.start()
    sub_num2.start()

# sum_num1: 1000000
# sum_num2: 2000000

注意事项:

14. 解释一下互斥锁产生的 死锁问题


死锁问题:
上锁后,如果不设置release()进行解锁的话,就会产生死锁。

import threading

mutex = threading.Lock()

def sum_num1():
    num = 1
    # 2. 上锁
    mutex.acquire()

    if num == 1:
        # 这里返回了空字符串,下面的release()方法没执行,因此会造成死锁。
        # 因为第一个线程死锁了,因此第二个线程不会被执行的!!
        return ''

    # 3. 解锁(如果不解锁,就会产生死锁问题)
    mutex.release()

def sum_num2():
    mutex.acquire()
    print("sum_num2:")
    mutex.release()

if __name__ == '__main__':
    sub_num1 = threading.Thread(target=sum_num1)
    sub_num2 = threading.Thread(target=sum_num2)

    sub_num1.start()
    sub_num2.start()

以上是关于Python 多进程和多线程 的使用的主要内容,如果未能解决你的问题,请参考以下文章

python 多进程和多线程配合

Python多线程和多进程谁更快?

Python 多进程和多线程

122 Python程序中的多进程和多线程

Python的多线程和多进程模块对比测试

python多进程和多线程的区别