04Python之---多任务编程、线程、进程

多任务优点:充分利用CPU资源,提高程序执行效率

并发:在同一时间内交替执行任务。
并行:多核CPU是并行的执行多任务,始终有多个软件一起执行。

一、进程和线程的概念

1.进程
进程是程序的基本执行实体,即正在运行的程序,会占用对应的内存区域;而程序只是一个静态的指令集合。
线程是操作系统os能够进行运算调度的最小单位,被包含在进程中,是进程中的实际运作单位。也是进程中的控制单元,负责进程中的程序执行。
一个进程可以开启多个线程,其中由一个主线程来调用本进程中的其他线程。

进程与线程的关系:
在这里插入图片描述
线程和进程的区别:
1)根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。
2)资源开销:进程都有独立的代码和数据空间,程序之间的切换开销大;线程可以看成是轻量级的进程,同一类线程共享代码和数据空间,线程也都有独立的运行栈和程序计数器,线程之间切换的开销小。
3)进程包含线程,一个进程中有一个主线程,或一个主线程+多个其他线程。
4)内存分配:同一个进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的。
5)影响关系:一个进程崩溃后,基本不会对其他进程产生影响,但是一个线程崩溃则会导致整个进程挂掉。
6)执行情况:每个独立的进程由程序运行的入口、顺序执行序列和程序出口。线程不是独立执行的,必须依赖应用程序。

二、进程

一个正在运行的程序或者软件就是进程。它是操作系统进行资源分配的基本单位。每启动一个进程,操作系统就会分配一定的运行资源(内存)保证进程的运行。

创建进程就是在内存中申请一块内存空间将需要运行的代码丢进去
一个进程对应在内存中就是一块独立的内存空间
多个进程对应在内存中就是多块独立的内存空间
进程与进程之间数据默认情况下是无法直接交互,如果想交互可以借助于第三方工具、模块。
注意:并不是所有情况下多线程都能够实现并发的效果:GIL。
GIL是否的情况:

  • 执行c代码
  • 执行IO
  • C模块进行释放
    不释放的情况:
  • 执行python字节码
  • 充分使用多核CPU并行性能。

所以将任务分为2类:

  • IO密集型(多线程)
  • CPU密集型(多进程)

进程使用三步骤:
1.导入进程包
2.创建子进程
3.启动进程执行对应的任务

主进程和子进程代码实现:

# 1.导入进程包
import multiprocessing
import time

#创建任务
def dance():
    for i in range(3):
        print("舞蹈中。。。")
        time.sleep(3)

def sing():
    for i in range(3):
        print("唱歌中。。。")
        time.sleep(3)

# 2.创建子进程
'''
group:进程组,目前只能使用None,一般不需要设置
target:进程执行的目标任务
name:进程名,如果不设置,默认是Process-1
'''

if __name__ == '__main__':
    dance_pro=multiprocessing.Process(target=dance)
# 3.启动进程执行对应的任务
    dance_pro.start()

#主进程执行唱歌
    sing()

多进程多任务代码:

dance_pro=multiprocessing.Process(target=dance)
sing_pro=multiprocessing.Process(target=sing)
# 3.启动进程执行对应的任务
if __name__ == '__main__':
    dance_pro.start()
    sing_pro.start()

获取进程编号:

获取当前进程编号:
os.getpid()
获取父进程编号:
os.getppid()

'''
1.导入进程包
2.创建子进程
3.启动进程执行对应的任务
'''

# 1.导入进程包
import multiprocessing
import os
import time


#创建任务
def dance():
    #获取当前进程pid
    dance_pid=os.getpid()
    print("dance_pid:",dance_pid,multiprocessing.current_process())
    for i in range(3):
        print("舞蹈中。。。")
        time.sleep(3)


def sing():
    #获取当前进程pid
    sing_pid=os.getpid()
    print("sing_pid:",sing_pid,multiprocessing.current_process())
    sing_parent_pid=os.getppid()
    print("sing的父进程pid:",sing_parent_pid)
    for i in range(3):
        print("唱歌中。。。")
        time.sleep(3)
        #根据pid强制杀死指定进程,该进程只会执行一次
        #os.kill(sing_pid,9)


# 2.创建子进程

dance_pro=multiprocessing.Process(target=dance,name='dance')
sing_pro=multiprocessing.Process(target=sing,name='sing')
# 3.启动进程执行对应的任务
if __name__ == '__main__':
    dance_pro.start()
    sing_pro.start()

    # 获取主进程编号
    main_pid = os.getpid()
    print('main_pid:', main_pid, multiprocessing.current_process())


result:
main_pid: 17784 <_MainProcess name='MainProcess' parent=None started>
dance_pid: 16208 <Process name='dance' parent=17784 started>
舞蹈中。。。
sing_pid: 13308 <Process name='sing' parent=17784 started>
sing的父进程pid: 17784
唱歌中。。。
舞蹈中。。。
唱歌中。。。
舞蹈中。。。
唱歌中。。。

执行带参数的任务

Process类执行任务并给任务传参数有以下两种方式:

  • args表示以元组的方式给执行任务传参
  • kwargs以字典方式给执行任务传参
# 1.导入进程包
import multiprocessing
import os
import time

#创建任务
def dance(name,active):
    for i in range(1):
        print("主题:",name,";动作:",active)
        time.sleep(3)

# 2.创建子进程
#dance_pro=multiprocessing.Process(target=dance,args=('黑天鹅','旋转'))
#result:主题: 黑天鹅 ;动作: 旋转
# dance_pro=multiprocessing.Process(target=dance,kwargs={"name":'芭蕾',"active":"踮起脚尖"})
#result:主题: 芭蕾 ;动作: 踮起脚尖
dance_pro=multiprocessing.Process(target=dance,args=('恰恰',),kwargs={"active":"左摆右摆,前摆后摆~"})
#result:主题: 恰恰 ;动作: 左摆右摆,前摆后摆~

# 3.启动进程执行对应的任务
if __name__ == '__main__':
    dance_pro.start()

创建子进程,相当于对主进程执行的代码拷贝执行,就会导致无限递归创建子进程,会报错。

设置主进程主动结束

主进程会等待子进程执行完成以后程序再退出,但是如果执行的数量多,就会出现问题,如死循环。

需求:只让主进程执行1s就自动结束
解决方法:
1.让子进程设置为守护主进程,主进程退出子进程销毁,子进程会依赖主进程
2.主进程退出之前先让子进程销毁


# 1.导入进程包
import multiprocessing
import os
import time


#创建任务
def dance():
    while True:
        print("子进程hhhhhhhh")
        time.sleep(1)


if __name__ == '__main__':
    dance_pro=multiprocessing.Process(target=dance)
    #1.让子进程设置为守护主进程,主进程退出子进程销毁,子进程会依赖主进程
    #dance_pro.daemon=True

    dance_pro.start()
    time.sleep(1)
    2.主进程退出之前先让子进程销毁
    dance_pro.terminate()
    print("主进程over")

进程池

用法跟线程池是一样的。

with ProcessPoolExecutor() as pool:
	pool.map(fun,参数)

三、线程

线程是进程中执行代码的一个分支,每个线程想要工作执行代码需要CPU进行调度。线程是CPU调度的基本单位。每个进程至少有一个线程,这个线程就是常说的主线程。

多线程效果图:
在这里插入图片描述
线程代码实现:

'''
@pROJECT:multipro
@Author:pengqi
@Date:2022/5/29
'''
import os
import threading

def threadmethod():
    thread_pid=os.getpid()
    print("`thread1线程pid:",thread_pid,threading.current_thread())
    print("thread11111....")

def threadmethod1():
    thread_pid = os.getpid()
    print("`thread2线程pid:", thread_pid, threading.current_thread())

    print("thread22222....")

thread1=threading.Thread(target=threadmethod,daemon=True)#创建线程是守护线程
thread2=threading.Thread(target=threadmethod1)

if __name__=='__main__':
    thread_pid=os.getpid()
    print("线程pid:",thread_pid,threading.current_thread())

    thread2.start()
    thread1.start()

result:
 线程pid: 17172 <_MainThread(MainThread, started 17168)>
`thread2线程pid: 17172 <Thread(Thread-2 (threadmethod1), started 1032)>
thread22222....
`thread1线程pid: 17172 <Thread(Thread-1 (threadmethod), started 16484)>
thread11111....   

线程的传参方式跟进程一样,参考以上进程的代码即可,不一一列举。

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

'''
共享全局变量
'''
import os
import threading
import time

sing=[]

def threadmethod():
    for i in range(5):
        sing.append(i)
    print('thread111',sing)

def threadmethod1():
    print("thread22222....",sing)

thread1=threading.Thread(target=threadmethod)
thread2=threading.Thread(target=threadmethod1)

if __name__=='__main__':
    thread1.start()
    thread2.start()
    # time.sleep(0.3)

    #让当前线程执行完,再执行主线程
    thread2.join()
    print("main thread over")

result:
thread111 [0, 1, 2, 3, 4]
thread22222.... [0, 1, 2, 3, 4]
main thread over

需求:让两个线程循环执行100万次,每次循环加1。按照常规思想来说,最后的结果是200w,但是实际上比200w少。
分析思路:
在这里插入图片描述

'''
数据错误,
'''
import os
import threading
import time

sing=0

def threadmethod():
    for i in range(1000000):
        global  sing
        sing+=1
    # print("thread1:",sing)


def threadmethod1():
    for i in range(1000000):
        global  sing
        sing+=1
    # print("thread2:",sing)


thread1=threading.Thread(target=threadmethod)
thread2=threading.Thread(target=threadmethod1)

if __name__=='__main__':
    thread1.start()
    thread2.start()
    # time.sleep(0.3)
    print("main:", sing)

解决方法:在任一个线程添加join,主线程等待该线程执行完,再执行。
如:thread1.joini()

线程池

from concurrent.futures import ThreadPoolExecutor
from threading import *
import time


def f(*args):
    time.sleep(1)
    print("线程之执行")

with ThreadPoolExecutor(max_workers=6) as pool:
    print("创建线程后,活跃的线程数:",active_count())

    #通过遍历的形式,将一系列的任务提交到线程池中
    pool.map(f,range(5))
    print("激活线程后,活跃的线程数:",active_count())

a=input("请输入内容:")

print(a*3)
print("线程任务完成后,活跃的线程数:",active_count())

在这里插入图片描述

线程同步

使线程同步有两种方法:
1.线程等待(join)
2.互斥锁

终止线程

有以下几种方式:

  • 如线程中有循环,终止循环,线程就停止
  • 如果run反复出现异常,则线程停止
  • 如果线程是守护线程,那么主线程结束得时候,线程会停止。
    • 如果线程是非守护线程,那么主线程结束得时候,python持续会等到线程执行完毕后,再结束
    • 主线程创建线程,默认是非守护线程

为什么要用锁?
解决多个线程同时执行同一个资源,可能会出问题,为了保护资源,也为了保障数据的准确性,对资源进行同步操作。

互斥锁

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

注意:

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

小结:

  • 互斥锁就是保证同一时刻只有一个线程操作共享数据,保证共享数据不会出现错误问题
  • 使用互斥锁的好处:确保某段关键代码只能由一个线程从头到尾完整区执行
  • 使用互斥锁会影响代码的执效率,多任务改成了单任务执行
  • 互斥锁如果没有用好容易出现死锁问题

步骤:
1.创建锁
2.获取锁,等待直到获取到锁。通过获取锁,可以让其他线程暂时停下来。
2.释放锁,让锁可以备货区,获取到锁的线程继续执行;没有获取到的线程继续等待。

with lock: 上下文管理器,自动获取,释放锁
	_i=i
	logging.info(f'get {i=}')
	time.sleep(0.5)
	i=_i-1
	logging.info(f'put {i=})
'''
@pROJECT:multipro
@Author:pengqi
@Date:2022/5/29
'''
import os
import threading
import time

sing=0

# 创建互斥锁lock,本质上是一个函数,通过调用函数创建锁
lock=threading.Lock()

def threadmethod():
    #上锁
    lock.acquire()
    for i in range(1000000):
        global  sing
        sing=sing+1

    print("thread1:",sing)

    #释放锁
    lock.release()


def threadmethod1():
    #上锁
    lock.acquire()
    for i in range(1000000):
        global  sing
        sing+=1
    print("thread2:",sing)

    #释放锁
    lock.release()


thread1=threading.Thread(target=threadmethod)
thread2=threading.Thread(target=threadmethod1)

if __name__=='__main__':
    thread1.start()
    thread2.start()

result:
thread1: 1000000
thread2: 2000000

RLock可重用锁

  • 同一时刻,只能被一个线程获取
  • 在一个线程中可以被多次获取
  • 获取了多少次,就应该释放多少次

Semaphore信号量

  • 同一时刻,只能被一些线程获取
  • 保护资源同时只被指定数量的线程操作
lock=threading.RLock()

def threadmethod():
    #上锁
    lock.acquire()
    lock.acquire()
    for i in range(1000000):
        global  sing
        sing=sing+1

    print("thread1:",sing)

    #释放锁
    lock.release()
    lock.release()

死锁

一直等待对方释放锁的情况

  • 对同一个锁,获取多次,从第二次开锁阻塞,导致没有机会释放
  • 互相等待: 获取A锁,等待B锁,但是B锁在A释放后才能释放B
'''
@pROJECT:multipro
@Author:pengqi
@Date:2022/5/29
'''
import os
import threading
import time

sing=[1,2,3]

# 创建互斥锁lock,本质上是一个函数,通过调用函数创建锁
lock=threading.Lock()

def getValues(index):
    #上锁
    lock.acquire()
    global sing

    if i >=len(sing):
        print("越界下标:",index)
        #释放锁,若该位置没有释放锁,则会导致死锁
        lock.release()
        return

    print(sing[index])
    #释放锁
    lock.release()

if __name__=='__main__':
    for i in range(10):
        new_thread=threading.Thread(target=getValues,args=(i,))
        new_thread.start()

四、线程与进程的关系

1.线程是依附在进程里的,没有进程就没有线程
2.一个进程至少有一个线程。

区别:

对比项目线程进程
全局变量相互共享,需要注意资源竞争问题。解决方法:互斥锁或线程同步不共享
基本单位CPU调度操作系统资源分配
是否独立执行否,需要依赖在进程中执行
优点资源开销小可以用CPU的多核
缺点不能使用多核资源开销大

多进程开发比单进程多线程开发稳定性强,某个进程挂掉不会影响其他进程。

如在线听音乐app开发:音乐播放是一个进程,音乐下载是一个进程,若一个进程挂了不会影响另一个功能的使用。若是单进程开发,那整个功能都不能用了。缺点是资源消耗会更大。

只有跟计算密切相关的,想要提高计算能力,这时候可以开发多进程,并且可以运用CPU的多核。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值