python进阶之多进程多线程

一.基础概念

1.并行与并发

并行:在一段时间内同时执行多个任务(多进程)

并发:在一段时间内快速交替执行多个任务(多线程)

2.什么是进程(Process)?

1)进程是操作系统进行资源分配的基本单位

2)进程可以有一个或多个子进程

3)最原始的父进程是操作系统提供的

3.什么是线程(Thread)?

1)线程是CPU进行调度的基本单位

2)线程可以有一个或多个子线程

3)线程是进程主动创建出来的,创建第一个子线程的时候才会出现主线程

4.线程的资源共享问题及解决办法(只存在python解释器3.6版本及以下)

线程资源共享问题:当多个线程同时操作一个共享的全局变量时,可能会出现错误的结果

解决办法:

1)线程同步:保证同一时刻只有一个线程去操作全局变量

线程等待thread.join():让一个线程完全执行结束之后,才去执行另一个线程

缺点:线程等待和单任务进行几乎没什么区别

互斥锁:多个线程去抢同一把锁,threading.Lock()抢到锁的进程执行,没抢到锁的线程会阻塞等待

缺点:虽然保障了多任务的有序执行,但是频繁的加锁,释放锁会大大增加程序的执行时间

2)从场景下手:不用多线程做累加count操作,制作append操作!

5.进程线程的对比

1)进程是操作系统资源分配的基本单位,线程是CPU进行调度的基本单位

2)线程不能独立执行,必须依存于进程

3)创建进程的资源消耗要远远大于创建线程的资源消耗

4)进程之间不共享全局变量,线程之间共享全局变量,但是在低版本python解释器要注意多线程之间的资源竞争问题

5)进程稳定性高,适合计算密集型任务;线程适合IO(输入输出)密集型任务

6)目前python多线程不能利用CPU多核心优势,想利用CPU多核心优势,只能采用多进程

二.多进程(多示例展示)

1.多进程示例

import time
import os
import multiprocessing
# os.getpid():process id,获取进程的编号
# os.getppid():parent process id,获取父进程编号

# 跳舞函数
def dance():
    print(f'dance进程的编号:{os.getpid()},父进程编号:{os.getppid()}')
    for i in range(5):
        print('跳舞中...')
        # 休眠1秒钟
        time.sleep(1)

# 唱歌函数
def sing():
    print(f'sing进程的编号:{os.getpid()},父进程编号:{os.getppid()}')
    for i in range(5):
        print('唱歌中...')
        # 休眠1秒钟
        time.sleep(1)

if __name__ == '__main__':
    # 最原始的父进程是由操作系统提供的
    print(f'主进程的编号为:{os.getpid()},父进程编号:{os.getppid()}')
    # 创建两个进程,一个执行 dance 函数,另一个执行 sing 函数
    dance_process = multiprocessing.Process(target=dance)
    sing_process = multiprocessing.Process(target=sing)
    # 启动进程
    dance_process.start()
    sing_process.start()

2.多进程执行带有参数的任务(示例)

import multiprocessing
import time

# 带有参数的任务(函数)
def task(count):
    for i in range(count):
        print('任务执行中...')
        time.sleep(0.2)
    else:
        print('任务执行完成')

if __name__ == '__main__':
    # 传参方式1 args=(参数值1, ...)
    # task_process = multiprocessing.Process(target=task, args=(3,))
    # 传参方式2 kwargs={'形参字符串1': 值1, '形参字符串2': 值2, ...}
    task_process = multiprocessing.Process(target=task, kwargs={'count': 5})
    # 启动进程
    task_process.start()
    task_process.join()
    print('这行代码一定等task_process进程执行完毕之后才会运行,打印')

3.进程之间不共享变量(示例)

import multiprocessing
import time

# 定义全局变量
g_list = []

# 添加数据的函数
def add_data():
    for i in range(5):
        g_list.append(i)
        print('add:', i)
        time.sleep(0.2)
    print('add:', g_list)

# 读取数据的函数
def read_data():
    print('read:', g_list)

if __name__ == '__main__':
    # 创建添加数据的子进程
    add_data_process = multiprocessing.Process(target=add_data)
    # 创建读取数据的子进程
    read_data_process = multiprocessing.Process(target=read_data)

    # 启动添加数据子进程
    add_data_process.start()
    # 阻塞等待:主进程等待 add_data_process 执行完成,再向下继续执行
    add_data_process.join()
    # 启动读取数据子进程
    read_data_process.start()
    # 主进程读取数据
    print('main:', g_list)

    # 主进程延时 1s
    time.sleep(1)
    print('主进程结束!')

4.子进程设为守护进程(示例)

守护进程:主进程结束,设为守护进程的子进程主动结束

import multiprocessing
import time

def task():
    for i in range(10):
        print('任务执行中...')
        time.sleep(0.5)

if __name__ == '__main__':
    # 创建子进程并启动
    sub_process = multiprocessing.Process(target=task)
    # TODO:设置子进程为守护进程
    sub_process.daemon = True
    sub_process.start()

    # 主进程延时 1s
    time.sleep(1)
    print('主进程结束!')

4.主进程终止子进程

子进程被动结束

import multiprocessing
import time


# 任务函数
def task():
    for i in range(10):
        print('任务执行中...')
        time.sleep(0.5)


if __name__ == '__main__':
    # 创建子进程并启动
    sub_process = multiprocessing.Process(target=task)
    sub_process.start()

    # 主进程延时 1s
    time.sleep(1)
    print('主进程结束!')
    # TODO: 终止子进程
    sub_process.terminate()

三.线程(多示例展示)

1.多线程(示例)

import time
import threading

# 跳舞函数
def dance(num):
    for i in range(num):
        print('跳舞中...')
        time.sleep(1)

# 唱歌函数
def sing(num):
    for i in range(num):
        print('唱歌中...')
        time.sleep(1)

if __name__ == '__main__':
    # 创建两个线程,分别执行 dance 和 sing
    dance_thread = threading.Thread(target=dance, args=(5,))
    sing_thread = threading.Thread(target=sing, kwargs={'num':5})
    # 启动线程
    dance_thread.start()
    sing_thread.start()

2.线程共用全局变量(示例) 

join()总结:

1- 作用:线程等待子线程执行结束

2- 使用场景:主线程需要使用多个子线程运行的最终结果,才能够继续往下运行.也可以解决多线程之间竞争资源的问题

import threading
import time

# 定义全局变量
g_list = []

# 添加数据的函数
def add_data():
    for i in range(5):
        g_list.append(i)
        print('add:', i)
        time.sleep(0.2)

    print('add:', g_list)

# 读取数据的函数
def read_data():
    print('read:', g_list)


if __name__ == '__main__':
    # 创建添加数据的子线程
    add_data_thread = threading.Thread(target=add_data)
    # 创建读取数据的子线程
    read_data_thread = threading.Thread(target=read_data)

    # 启动添加数据子线程
    add_data_thread.start()
    # 阻塞等待:主线程等待 add_data_thread 执行完成,再向下继续执行
    add_data_thread.join()

    # 启动读取数据子线程
    read_data_thread.start()
    # 阻塞等待:主线程等待 read_data_thread 执行完成,再向下继续执行
    read_data_thread.join()

    print('main:', g_list)

 3.线程互斥锁(在低版本python解释器应用较广)

# 互斥锁:多个线程去抢同一把"锁",抢到锁的线程执行,没抢到锁的线程会阻塞等待
import threading

# 定义全局变量
g_num = 0

# 创建一个多线程互斥锁
lock = threading.Lock()

def sum_num1():
    global g_num
    # 循环一次给全局变量加1
    for i in range(1000000):
        # 抢到锁,代码可以继续向下执行,否则就会阻塞等待
        lock.acquire() # 抢锁
        g_num += 1
        lock.release() # 释放锁
    print('sum1:', g_num)


def sum_num2():
    global g_num
    # 循环一次给全局变量加1
    for i in range(1000000):
        # 抢到锁,代码可以继续向下执行,否则就会阻塞等待
        lock.acquire() # 抢锁
        g_num += 1
        lock.release() # 释放锁
    print('sum2:', g_num)

if __name__ == '__main__':
    # 创建两个线程
    first_thread = threading.Thread(target=sum_num1)
    second_thread = threading.Thread(target=sum_num2)
    # 启动两个线程
    first_thread.start()
    second_thread.start()

    # 阻塞等待:主线程等待子线程结束再向下运行
    first_thread.join()
    second_thread.join()

    print(g_num)

 四.知识点总结

1-多进程:计算机分配资源的最小单位

2-多线程:CPU进行调度的最小单位

3-线程不能独立存在,必须依存于进程中

4-多进程中,多个子进程和主进程之间,不会共享全局变量

5-多线程中,多个子线程和主线程之间,会共享全局变量

6-将子线程设置为守护线程.只有设置为守护线程的子线程才会在主线程结束后也跟着结束,其他不受影响.

7-多线程中,使用join可以让线程等待另一个线程执行结束再开始

猜你喜欢

转载自blog.csdn.net/MSJ3917/article/details/135287239