python入门(10)——python中的多进程与多线程、互斥锁、死锁

1.多任务的执行方式

  • 并发
  • 并行

并发:

在一段时间内交替去执行任务。

例如:

对于单核cpu处理多任务,操作系统轮流让各个软件交替执行,假如:软件1执行0.01秒,切换到软件2,软件2执行0.01秒,再切换到软件3,执行0.01秒……这样反复执行下去。表面上看,每个软件都是交替执行的,但是,由于CPU的执行速度实在是太快了,我们感觉就像这些软件都在同时执行一样,这里需要注意单核cpu是并发的执行多任务的。

并行:

对于多核cpu处理多任务,操作系统会给cpu的每个内核安排一个执行的软件,多个内核是真正的一起执行软件。这里需要注意多核cpu是并行的执行多任务,始终有多个软件一起执行。

小结

使用多任务就能充分利用CPU资源,提高程序的执行效率,让你的程序具备处理多个任务的能力。
多任务执行方式有两种方式:并发和并行,这里并行才是多个任务真正意义一起执行。

2. 进程的使用

# 1. 导入multiprocessing包
import multiprocessing
import time


# 定义跳舞的任务函数
def dance():
    for i in range(3):
        print("跳舞。。。")
        time.sleep(0.2)


# 定义唱歌的任务函数
def sing():
    for i in range(3):
        print("唱歌。。。")
        time.sleep(0.2)

# 判断是否是主模块
if __name__ == '__main__':
    #sing()
    #dance()
    '''
        唱歌。。。
        唱歌。。。
        唱歌。。。
        跳舞。。。
        跳舞。。。
        跳舞。。。
    '''

    # 2. 创建进程对象
    # group : 进程组,目前只能使用None, 一般不要管它
    # target: 指定执行的任务名
    sub_process1 = multiprocessing.Process(target=dance)
    sub_process2 = multiprocessing.Process(target=sing)


    # 3. 启动进程执行对应的任务
    sub_process1.start()
    sub_process2.start()
     '''
        唱歌。。。
        跳舞。。。
        唱歌。。。
        跳舞。。。
        唱歌。。。
        跳舞。。。
    '''

3.获取当前进程的编号

import time
import multiprocessing
import os

def sing():
    # 获取当前的进程对象,并打印
    sing_process = multiprocessing.current_process()
    print("sing_process: ",sing_process.name)
    # 获取唱歌子进程的编号
    os_getpid = os.getpid()
    print(f"唱歌子进程的编号:{os_getpid}")
    # 获取当前进程的父进程
    parent_id = os.getppid()
    print(f"唱歌进程的父进程为:{parent_id}")
    # 唱歌逻辑实现
    for i in range(3):
        print("唱歌...")
        time.sleep(0.5)

def dance():
    # 获取当前的进程对象,并打印
    dance_process = multiprocessing.current_process()
    print("dance_process: ",dance_process.name)
    # 获取跳舞子进程的编号
    os_getpid = os.getpid()
    print(f"跳舞子进程的编号:{os_getpid}")
    # 获取当前进程的父进程
    parent_id = os.getppid()
    print(f"唱歌进程的父进程为:{parent_id}")
    # 跳舞逻辑实现
    for i in range(3):
        print("跳舞...")
        time.sleep(0.5)

if __name__ == '__main__':
 	# 获取当前进程(主进程)的编号
    getpid = os.getpid()
    print(f"主进程id为{getpid}")
    # 不使用进程
    # sing()
    # dance()
    '''
        主进程id为9288
        MainProcess
        唱歌子进程的编号:9288
        当前进程的父进程为:24552
        唱歌...
        唱歌...
        唱歌...
        MainProcess
        跳舞子进程的编号:9288
        当前进程的父进程为:24552
        跳舞...
        跳舞...
        跳舞...
    '''
    process_sing = multiprocessing.Process(target=sing)
    process_dance = multiprocessing.Process(target=dance)
    process_sing.start()
    process_dance.start()
    '''
        主进程id为2336
        sing_process:  Process-1
        唱歌子进程的编号:13024
        dance_process:  Process-2
        跳舞子进程的编号:16908
        唱歌进程的父进程为:2336
        唱歌...
        唱歌进程的父进程为:2336
        跳舞...
        唱歌...
        跳舞...
        唱歌...
        跳舞...
    '''

4.进程带有参数的任务

# 带参数执行任务:有两种方式,一种是直接以元组的方式进行,一种是以字典的形式进行
import multiprocessing

def task(name,count):
    print(name)
    print(multiprocessing.current_process())
    for i in range(count):
        print("%d执行任务" % i)

if __name__ == '__main__':
    # 创建子进程
    # 元组如果只有一个元素,那么元素后面的逗号不能省略
    # args: 表示以元组方式给执行任务传参数, 实际上是按照函数位置参数进行传参的。
    # 在创建进程的时候往进程中添加进程参数
    sub_process = multiprocessing.Process(target=task,args=("张三",3),name="sub_process")
    sub_process.start()
    '''
        张三
        <Process(sub_process, started)>
        0执行任务
        1执行任务
        2执行任务
    '''

    # 使用字典
    # sub_process = multiprocessing.Process(target=task, kwargs={"name":"张三","count":3})
    # sub_process.start()
    '''
        张三
        <Process(Process-1, started)>
        0执行任务
        1执行任务
        2执行任务
    '''

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

import time
import multiprocessing

# 测试
# result = type([])
# print(result) # <class 'list'>

# 定义全局变量
g_list =list()  # =》 [] 表示一个空列表

# 向全局变量中添加数据
def add_data():
    for i in range(1,3):
        g_list.append(i)
        print("全局变量添加数据:%d",i)
        time.sleep(0.5)
    print("数据添加完毕。。。",g_list)

# 读取全局变量的数据
def read_data():
    print("只读取数据进程读取:",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()
    # read_data_process.start()
    '''
        只读取数据进程读取: []
        全局变量添加数据:%d 1
        全局变量添加数据:%d 2
        数据添加完毕。。。 [1, 2]
    '''
    add_data_process.start()
    # 进程等待 join, 主进程会等待子进程(add_data_process)执行完成以后,再继续执行下面的代码
    add_data_process.join()
    read_data_process.start()

    print("获取主进程里面的g_list",g_list)
    '''
        全局变量添加数据:%d 1
        全局变量添加数据:%d 2
        数据添加完毕。。。 [1, 2]
        获取主进程里面的g_list []
        只读取数据进程读取: []
    '''

6.主进程会等待所有的子进程执行完成之后再退出

import time
import multiprocessing

def task():
    for i in range(3):
        print("子进程工作中。。。")
        time.sleep(0.5)

if __name__ == '__main__':
    # 创建子进程
    sub_process = multiprocessing.Process(target=task)
    # 创建主线程的守护进程,即主进程销毁的时候,守护进程也会直接销毁
    sub_process.daemon = True
    sub_process.start()
    # 主进程暂停0.6秒,如果不加这个主进程的休眠时间,子线程是执行不出结果的
    time.sleep(0.6)
    # 让子进程销毁
    # sub_process.terminate()
    print("主进程over")
    # exit()

    # 默认:主进程会等待所有的子进程执行完成以后主进程再退出

    # 主进程不等待子进程的操作
    # 1. 主进程在退出执行,先让子进程进行销毁 sub_process.terminate()
    # 2. 设置守护主进程,主进程退出子进程直接销毁,不再执行子进程里面的任务

7.线程的使用

import time
import threading

def sing():
    for i in range(3):
        print("唱歌。。。")
        time.sleep(0.5)
def dance():
    for i in range(3):
        print("跳舞...")
        time.sleep(0.5)
if __name__ == '__main__':
    sub_thread1 = threading.Thread(target=sing)
    sub_thread2 = threading.Thread(target=dance,name="sub_thread2")
    print(sub_thread1,sub_thread1.name)
    print(sub_thread2,sub_thread2.name)
    sub_thread1.start()
    sub_thread2.start()
    '''
        <Thread(Thread-1, initial)> Thread-1
        <Thread(sub_thread2, initial)> sub_thread2
        唱歌。。。
        跳舞...
        跳舞...
        唱歌。。。
        跳舞...
        唱歌。。。
    '''

8.线程带有参数执行任务

import threading

def task(name,age):
    print("测试")
    task_thread = threading.current_thread()
    print("task_thread:",task_thread)
    print("姓名:%s,年龄:%d" % (name,age))
if __name__ == '__main__':
    main_thread = threading.current_thread()
    print("main_thread:",main_thread)
    sub_thread = threading.Thread(target=task, kwargs={"name": "貂蝉", "age": 19})
    sub_thread.start()

9.线程之间的执行是无序的

import threading
import time

def task():
    time.sleep(0.2)
    print(threading.current_thread().name)

if __name__ == '__main__':
    my_list = []
    for i in range(10):
        sub_thread = threading.Thread(target=task)
        my_list.append(sub_thread)

    for value in my_list:
        value.start()
'''
    Thread-2Thread-3

    Thread-1
    Thread-6
    Thread-7
    Thread-8
    Thread-4Thread-5
    
    Thread-9
    Thread-10
'''

10.主线程会等待所有的子线程执行结束再结束

import threading
import time

def task():
    for i in range(10):
        print("子线程。。。")
        time.sleep(0.3)

if __name__ == '__main__':
    # 创建子线程
    # 1. daemon=True 表示守护主线程,主线程退出子线程销毁
    # sub_thread = threading.Thread(target=task, daemon=True)
    # 2. setDaemon(True)表示守护主线程
    sub_thread = threading.Thread(target=task)
    # sub_thread.setDaemon(True)

    sub_thread.start()

    # time.sleep(1)
    print("主线程结束。。。")
    # 总结:
    # 默认情况下,主线程会等待所有的子线程执行完成以后主线程再退出

    # 解决办法:
    # 1. 守护主线程: 1.1 setDaemon(True), 1.2 daemon=True

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

import threading
import time

# 定义全局变量列表
g_list = []


# 向全局变量里面添加数据
def add_data():
    for i in range(4):
        g_list.append(i)
        print("add_data:", i)
        time.sleep(0.2)

    # 代码执行到此
    print("数据添加完成:", g_list)


def read_data():
    print("read_data:", g_list)

if __name__ == '__main__':
    sub_thread1 = threading.Thread(target=add_data)
    sub_thread2 = threading.Thread(target=read_data)

    sub_thread1.start()
    # time.sleep(1)
    # 线程等待, 主线程等待添加数据线程执行完成以后,代码再继续往下执行

    sub_thread2.start()

12.线程之间共享全局变量出现错误问题

import threading

# 定义全局变量
g_num = 0


# 循环1000000次,每循环一次给全局变量加1
def calc_num1():
    # 声明此处加上global表示要修改全局变量的内存地址
    global g_num
    for i in range(1000000):
        g_num += 1

    print("calc_num1:", g_num)


# 循环1000000次,每循环一次给全局变量加1
def calc_num2():
    # 声明此处加上global表示要修改全局变量的内存地址
    global g_num
    for i in range(1000000):
        g_num += 1

    print("calc_num2:", g_num)


if __name__ == '__main__':
    # 创建第一个子线程
    first_thread = threading.Thread(target=calc_num1)
    # 创建第二个子线程
    second_thread = threading.Thread(target=calc_num2)

    # 启动线程执行任务
    first_thread.start()
    # 主线程等待第一个子线程执行完成以后程序再执行(线程同步)
    first_thread.join()
    second_thread.start()
'''
    calc_num2: 10000
    calc_num1: 20000
'''

线程注意点总结:

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

13.互斥锁

互斥锁同样解决全局共享数据读取问题:

import threading

# 定义全局变量
g_num = 0

# 定义一把锁
lock = threading.Lock()


# 循环1000000次,每循环一次给全局变量加1
def calc_num1():
    # 在执行这个函数的时候先上锁
    lock.acquire()
    # 声明此处加上global表示要修改全局变量的内存地址
    global g_num
    for i in range(1000000):
        g_num += 1

    print("calc_num1:", g_num)
    # 函数执行完毕以后,释放锁对象
    lock.release()


# 循环1000000次,每循环一次给全局变量加1
def calc_num2():
    # 在执行这个函数之前上锁
    lock.acquire()
    # 声明此处加上global表示要修改全局变量的内存地址
    global g_num
    for i in range(1000000):
        g_num += 1

    print("calc_num2:", g_num)

    # 函数执行完毕之后
    lock.release()


if __name__ == '__main__':
    # 创建第一个子线程
    first_thread = threading.Thread(target=calc_num1)
    # 创建第二个子线程
    second_thread = threading.Thread(target=calc_num2)

    # 启动线程执行任务
    first_thread.start()
    # 主线程等待第一个子线程执行完成以后程序再执行(线程同步)
    # first_thread.join()
    second_thread.start()
    # 结论: 互斥锁可以解决全局变量数据错误问题,互斥锁可以保证同一时刻只有一个线程取值

    # 性能: 执行代码的效率会下降,能够保证数据的安全性。
'''
    calc_num2: 10000
    calc_num1: 20000
'''

14.死锁

import threading


# 创建互斥锁
lock = threading.Lock()


# 保证同一时刻只有一个线程根据下标去取值
def get_value(index):

    # 取值之前先上锁
    lock.acquire()
    my_list = [1, 4, 8]

    # 判断下标是否越界
    if index >= len(my_list):
        print("下标越界:", index)
        # 下标越界不能取值,也需要把锁释放,保证后面的线程可以再次取值
        lock.release()
        return

    # 根据下标获取值
    result = my_list[index]

    print(result)
    # 取值完成需要释放锁
    lock.release()


if __name__ == '__main__':
    # 创建很多线程,同时执行某个任务
    for i in range(5):
        # 创建子线程
        sub_thread = threading.Thread(target=get_value, args=(i,))
        # 启动线程执行任务
        sub_thread.start()
         '''
            1
            4
            8
            下标越界: 3
            下标越界: 4
        '''

15.进程与线程的对比

1. 进程和线程的对比的三个方向

关系对比
区别对比
优缺点对比

1. 关系对比

  1. 线程是依附在进程里面的,没有进程就没有线程。
  2. 一个进程默认提供一条线程,进程可以创建多个线程。
    对比
    在这里插入图片描述

2. 区别对比

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

  2. 线程之间共享全局变量,但是要注意资源竞争的问题,解决办法: 互斥锁或者线程同步

  3. 创建进程的资源开销要比创建线程的资源开销要大

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

  5. 线程不能够独立执行,必须依存在进程中

  6. 多进程开发比单进程多线程开发稳定性要强

3. 优缺点对比

进程优缺点:
优点:可以用多核
缺点:资源开销大

线程优缺点:
优点:资源开销小
缺点:不能使用多核

4. 小结

  1. 进程和线程都是完成多任务的一种方式
  2. 多进程要比多线程消耗的资源多,但是多进程开发比单进程多线程开发稳定性要强,某个进程挂掉不会影响其它进程。
  3. 多进程可以使用cpu的多核运行,多线程可以共享全局变量。
  4. 线程不能单独执行必须依附在进程里面
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值