【python】多进程和多线程

【Python】多线程

概念

同一时间内能够执行多个任务

执行方式

并发:多个任务交替去运行

并发:多喝CPU中,多个任务是在多个CPU上运行的

一般来说:并发和并行是同时存在的,是操作系统自动调用的

实现方式

  • 多进程实现
  • 多线程实现
  • 协程实现

进程的概念

概念
  • 程序:安装的程序,写的代码
  • 进程:运行起来的程序就是进程,是操作系统进行资源分配的基本单位
  • 线程:一个进程中会默认有一个线程,线程是真正执行任务的
作用

进程是实现多任务的一种方式

进程状态
  • 新建态 创建一个进程,分配资源
  • 就绪态 只差时间片
  • 运行态 获取到了时间片
  • 阻塞态 等待外部条件的满足
  • 死亡态 进程执行结束

注意

  • 只能是有就绪态到运行态
  • 阻塞的条件满足,就再次进入就绪态,等待CPU

进程类

  • 导包
import multiprocessing
  • 函数
Process([group [,target [,name [,args [, kwargs]]]]])
'''
group: 指定进程组,目前只能None
target:执行的目标名
name : 进程名字
args: 以元组的方式给执行进程传参
kwargs:以字典的方式给执行进程传参
'''
  • Process常用的方法
start():启动子进程实例(创建子进程)
join():等待子进程结束
# 书写在哪个进程,哪个进程阻塞式等待
# 哪个进程.join() 就是等待哪个进程执行结束
terminate():不管任务是是否完成,终止子进程
import multiprocessing
import time # 让进程休息多少秒


def sing():
    for i in range(5):
        print('唱歌')
        time.sleep(0.1) # 手动让进程进入阻塞态,单位是s


def dance():
    for i in range(5):
        print('跳舞')
        time.sleep(0.1)


if __name__ == '__main__':
    # 创建进程对象
    process1 = multiprocessing.Process(target=sing)
    process2 = multiprocessing.Process(target=dance)# 不能加()否则成了函数调用
    # 启动进程对象
    process1.start()
    process2.start()
    # 等待结束
    process1.join()
    process2.join()

image-20230422222512354

  • 获取进程的pid
# 获取当前的进程对象
multiprocessing.current_process()

import multiprocessing
import time
import os


def sing():
    print(multiprocessing.current_process().name) # 获取name
    for i in range(5):
        print('唱歌')
        time.sleep(0.1)


def dance():
    print(multiprocessing.current_process().name)
    for i in range(5):
        print('跳舞')
        time.sleep(0.1)


if __name__ == '__main__':
    # 获取当前进程的id
    print(multiprocessing.current_process().name)
    # 创建进程对象
    process1 = multiprocessing.Process(target=sing)
    process2 = multiprocessing.Process(target=dance)
    # 启动进程对象
    process1.start()
    process2.start()
    # 查看进程的pid
    # print(os.getpid())
    # 等待结束
    process1.join()
    process2.join()

image-20230422223426828

import os
os.getpid() # 获取进程的pid
os.getppid() # 获取进程的ppid
os.kill(pid,9) # 杀死指定的进程
# 获取进程的id的第二种方法:
multiprocessing.current_process().pid

进程注意点

  • 进程之间不共享全局变量
import multiprocessing
import time

g_list = []


def add_data():
    for i in range(5):
        g_list.append(i)
        print(f'添加了 {i}')
        time.sleep(0.1)
    print('add_data:', g_list)


def read():
    print('read:', g_list)


if __name__ == '__main__':
    process_add = multiprocessing.Process(target=add_data)
    process_read = multiprocessing.Process(target=read)

    process_add.start()
    process_add.join()  # 阻塞等待进程执行完毕

    process_read.start()
    process_read.join()

image-20230422225859645

  • 主进程会等待子进程执行结束再结束

主进程结束,意味着程序结束。理论上,一个进程结束了,主进程必须结束

import multiprocessing
import time


def func():
    for i in range(5):
        print(i)
        time.sleep(0.5)
    print('子进程运行结束')


if __name__ == '__main__':
    fork = multiprocessing.Process(target=func)
    fork.start()
    time.sleep(0.5)
    print('主进程运行结束,理论上程序结束')

image-20230422230747342

  • 想让子进程随着主进程结束而结束
    • 方法一:terminate()
# 使用方法 terminate()
import multiprocessing
import time


def func():
    for i in range(5):
        print(i)
        time.sleep(0.5)
    print('子进程运行结束')


if __name__ == '__main__':
    fork = multiprocessing.Process(target=func)
    fork.start()
    time.sleep(0.5)
    print('主进程运行结束...')
    fork.terminate()
'''
0
主进程运行结束...
'''
  • 方法二:将子进程设置为守护进程 daemon = True
if __name__ == '__main__':
    fork = multiprocessing.Process(target=func)
    fork.daemon = True  # 先设置再运行
    fork.start()
    time.sleep(0.5)
    print('主进程运行结束...')
   
'''
0
主进程运行结束...
'''

线程

  • 线程是进程中执行的一个的分支
  • 线程依附在继承中
  • 线程是CPU调度的基本单位

作用

实现多任务

多线程的使用

  • 导入线程模块
import threading
  • 创建线程对象
Tread([group [,target [,name [,args [, kwargs]]]]])
  • group 线程组,目前只能使用None

  • target 执行目标任务名

  • args 元组的形式传参

  • kwargs 字典的形式传参

  • name 线程名 一般不用设置

  • 线程启动

线程名.start()
import threading
import time


def sing():
    for i in range(5):
        print('sing')
        time.sleep(0.1)


def dance():
    for i in range(5):
        print('dance')
        time.sleep(0.1)


if __name__ == '__main__':
    thread1 = threading.Thread(target=sing)
    thread2 = threading.Thread(target=dance)
    thread1.start()
    thread2.start()
    
'''
sing
dance
sing
dance
sing
dance
sing
dance
sing
dance
'''
  • 查看线程名
threading.current_thread().name

线程传参

import threading
import time


def sing(name, work):
    for i in range(5):
        print(f'{name}sing{work}...')
        time.sleep(0.1)


def dance(name):
    for i in range(5):
        print(f'{name}dance...')
        time.sleep(0.1)


if __name__ == '__main__':
    thread1 = threading.Thread(target=sing, args=('周深', '大鱼海棠'))# 元组传参
    thread2 = threading.Thread(target=dance, kwargs={'name': '探戈'})# 字典传参
    thread1.start()
    thread2.start()
'''
周深sing大鱼海棠...
探戈dance...
探戈dance...
周深sing大鱼海棠...
周深sing大鱼海棠...
探戈dance...
探戈dance...周深sing大鱼海棠...

周深sing大鱼海棠...探戈dance...
'''

线程的注意点

  • 线程之间是无序的 – 就是抢占资源,所以是随机的
  • 主线程会等待所有的子线程执行结束再结束

证明:

import threading
import time


def sing():
    for i in range(5):
        print('sing...')
        time.sleep(0.1)


def dance():
    for i in range(5):
        print('dance...')
        time.sleep(0.1)


if __name__ == '__main__':
    thread1 = threading.Thread(target=sing)
    thread2 = threading.Thread(target=dance)
    thread1.start()
    thread2.start()
    time.sleep(0.3)
    print('主线程结束,理论上程序结束')

'''
sing...
dance...
sing...
dance...
sing...
dance...
主线程结束,理论上程序结束
sing...dance...

sing...dance...
'''

想让线程随着进程一起结束可以使用以下方法:

# 方法:设置为守护线程daemon = True
  • 线程之间共享全局变量
import threading

g_list = []


def add():
    for i in range(5):
        g_list.append(i)
    print(g_list)


def show_global():
    print(g_list)


if __name__ == '__main__':
    thread = threading.Thread(target=add)
    show_global()
    print('-' * 30)
    thread.start()
    print('-' * 30)
    show_global()
'''
[]
------------------------------
[0, 1, 2, 3, 4]
------------------------------
[0, 1, 2, 3, 4]
'''
  • 线程直接共享全局变量数据出现错误问题
# 创建2个线程,对全局变量进行处理10000000次,看全局变量的大小

import threading

g_num = 0


def run():
    global g_num
    for i in range(10000000):
        g_num += 1 # 该操作不是原子的
    print(threading.current_thread().name, g_num)


if __name__ == '__main__':
    thread1 = threading.Thread(target=run)
    thread2 = threading.Thread(target=run)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(f'g_num:{g_num}')
'''
# 每次的运行结果都不一样
Thread-2 12233499
Thread-1 12531189
g_num:12531189
'''

解决方法

  • 线程同步解决

阻塞式等待

	thread1.start()
    thread1.join() # 先搞完线程1 再去搞线程2
    thread2.start()
    thread2.join()
  • 互斥锁解决

互斥锁

概念

对共享数据进行锁定,保证同一时间只有一个线程去访问

作用

保护共享数据,避免资源竞争

使用
# 1.创建锁
mutex = threading.Lock()
# 2.上锁
mutex.acquire()
# 3,释放锁
mutex.release()
import threading

mutex = threading.Lock()
g_num = 0


def run():
    global g_num
    mutex.acquire()  # 上锁
    for i in range(10000000):
        g_num += 1
    mutex.release()  # 解锁
    print(threading.current_thread().name, g_num)


if __name__ == '__main__':
    thread1 = threading.Thread(target=run)
    thread2 = threading.Thread(target=run)
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(f'g_num:{g_num}')

'''
Thread-1 10000000
Thread-2 20000000
g_num:20000000
'''

死锁

概念

一直使得线程在等待

危害

在造成应用程序停止响应,不能再处理其他的任务

import threading

mutex1 = threading.Lock()
mutex2 = threading.Lock()
mutex3 = threading.Lock()
g_num = 0


def run():
    mutex1.acquire()
    mutex2.acquire()
    mutex3.acquire()
    print('run')
    return # 不释放锁直接返回


def printf():
    mutex3.acquire()
    mutex2.acquire()
    mutex1.acquire()
    print('printf')


if __name__ == '__main__':
    thread1 = threading.Thread(target=run)
    thread2 = threading.Thread(target=printf())
    thread1.start()
    thread2.start()

image-20230423172609638

进程和线程的对比

关系对比

  • 线程是依附在进程内部的,没有进程就没有线程
  • 一个进程默认只有一个线程,进程可以创建多个线程

关系对比

  • 进程不共享全局变量
  • 线程之间共享全局变量,但是要注意资源竞争的问题,解决方法,线程同步或者互斥锁
  • 创建进程的资源开销大于创建线程的
  • 进程是OS分配的基本单位,线程是CPU调度的基本单位
  • 线程不能独立执行,进程能够独立进行
  • 多线程开发比但进程多线程开发稳定性强

优缺点对比

  • 进程的优缺点

    • 优点:可以多核
    • 缺点:资源开销大
  • 现成的优缺点

    • 优点:资源开销小
    • 缺点:不能使用多核
  • 进程适合 计算密集型(大量的数学计算)

  • 线程适合 大量的IO密集型(读写操作,爬虫)

GIL

全局解释器锁

GIL保证同一时间,只有一个线程使用CPU

一个进程有一个锁,GIL不是python的特性,只是Cpython解释器的概念,历史遗留问题

GIL锁什么时候释放
  • 当前线程执行超时的时候
  • 再当前线程执行阻塞式等待会自动释放(input/io)
  • 当前执行完成时
GIL的弊端

GIL对计算密集型的程序会产生影响,因为计算密集型会占用系统资源

GIL相当于单线程运算

对IO密集型影响不大,因为等的输入输出足够消耗时间了,单线程,多线程都得等

解决方法
  • 更换解释器
  • 使用多继承解决多线程
  • 子进程使用C语言实现
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值