文章目录
进程
多任务
程序的运行都是cpu和内存协同工作的结果
常用的操作系统都支持“多任务”的操作
问题1:什么时多任务
操作系统上同时运行多个任务,除了手动打开应用程序之外,还有很多的后台程序
问题2:单核cpu是怎么执行多任务
操作系统会让多个任务轮流交替执行,由于不同的任务之间的切换速度很快,导致肉眼无法识别,
多核cpu实现多任务:真正的并行执行之恶能在多核cpu上执行,但是现实情况下,任务的数量可能会远远大于cpu的核心数量,操作系统会将每个任务调度,让每个cpu核心上轮流执行
并行:真正一起执行,任务的数量小于cpu的核心数量[理想型]
并发:看上去一起执行,任务数量大于cpu的核心数量[现实型]
问题3:什么是进程?
对于操作系统而言,一个任务就是一个进程[Process]
例如:打开一个浏览器启动了一个浏览器进程,打开一个Word启动了一个word进程,但是,一个进程可以同时干多件事情,在一个进程的内部,要同时干多个事情,就需要同时执行多个子任务,将一个进程中的子任务被称为线程[Thread]
进程本质
是对一个程序的运行状态和占用资源[cpu,内存]的描述
进程是程序的一个运动过程,他指的是从代码到加载到最后代码加载完毕的整个过程
进程的特点:
a.独立性:不同的进程之间是相互独立的,在操作系统中互不影响
b.动态性:进程一旦被启动之后,在操作系统中并不是静止不动的,一直处于动态状态
c.并发性:在操作系统上交替执行
python多任务实现方式
a.多进程模式:启动多个进程,每个进程虽然只有一个线程,通过该线程执行一个任务
b.多线程模式:启动一个进程,该进程启动多个线程,每个线程执行一个任务
c.协程模式:yield[函数生成器]
d:多进程模式+多线程模式:启动多个进程,每个进程启动多个线程,执行的任务的数量会更多
使用
单任务现象
import time
#单任务:一个函数执行了某个特定的功能
def run():
while True:
print("hello")
time.sleep(0.5)
if __name__ == "__main__":
while True:
print("main")
time.sleep(1)
run()
#说明:单任务现象代码从上往下依次执行,前面的代码执行完毕,后面的代码才有执行的机会
启动多进程实现多任务
import time,os
#如果要实现多任务,Python中提供了相应的模块
#每通过Process创建一个对象,则表示创建一个进程
from multiprocessing import Process
#子进程中的任务
def run(s):
#os.getpid()获取当前正在执行的进程id
#os.getppid(获取当前正在执行进程的父进程id
print("子进程启动:%s-%s" % (os.getpid(),os.getppid()))
print(s)
while True:
print("hello")
time.sleep(1)
if __name__ == "__main__":
#1.__main__中执行的代码表示主进程,也称为父进程
#os.getpid获取当前正在执行的进程id
print("主进程启动:%s" % (os.getpid()))
#2.在主进程中创建子进程
#Process(target,args),target表示子进程需要执行的任务【函数】,args表示需要执行的任务的参数,类型为元组
#2.1任务函数没有参数
#p1 = Process(target=run)
#2.2任务函数有参数
#注意:给子进程中的任务传参的时候,函数有几个参数,元组中的元素需要保持一致
p1 = Process(target=run,args=("text",))
#如果想要启动一个子进程,则必须调用start
p1.start()
#主进程中的任务
while True:
print("main")
time.sleep(1)
run()
父子进程的执行顺序
from multiprocessing import Process
import time,os
def run():
print("子进程启动~~~~~")
time.sleep(2)
print("子进程结束~~~~~")
if __name__ == "__main__":
print("父进程启动")
time.sleep(2)
p = Process(target=run)
p.start()
p.join()
print("父进程结束")
"""
默认情况下,都是主进程结束之后子进程才启动
使用join之后,主进程会等待子进程结束之后才结束
注意:join的调用只能在start之后
"""
多个进程中的全局变量
from multiprocessing import Process
from time import sleep
import os
#需求:在一个进程中修改全局变量的值,观察另一个进程中全局变量的值的变化
#全局变量
num = 100
def run():
print("子进程开始:%s" % (os.getpid()))
global num
num += 1
print("子进程结束:num=%d" % (num))
if __name__ == "__main__":
print("父进程开始:%s" % (os.getpid()))
p = Process(target=run)
p.start()
p.join()
print("父进程结束:num=%d" % (num))
"""
说明:在子进程中修改全局变量对父进程中的全局变量没有影响
工作原理;在创建子进程的时候,实际上对全局变量做了一个备份,父进程中的num和子进程中的num是两个不同的变量
进程是系统中程序执行和系统资源分配的基本单位,每个进程都有自己的数据段,代码段,堆栈段
【进程之间是相互独立的,资源不共享】
"""
验证多个进程之间的独立性
启动大量子进程
如果要大量创建子进程,可以使用进程池的方式批量创建子进程
#进程池
from multiprocessing import Pool
import os,time,random
#需求:创建多个子进程,让子进程执行同一个任务,根据传入的参数作为进程的名称进行区分
def run(name):
print("子进程%s启动:%s" % (name,os.getpid()))
#通过time计算耗时的时间差
start = time.time()
time.sleep(random.choice(range(1,4)))
end = time.time()
print("子进程%s结束,进程号:%s,耗时:%s" % (name, os.getpid(),end - start))
if __name__ == "__main__":
print("父进程启动")
#创建多个进程,采用进程池的方式,通过系统类Pool创建多个进程对象
#Pool(num),num表示需要同时执行的任务的数量,可以省略,默认为cpu的核心数量
p = Pool(8)
#通过循环的方式创建多个子进程
for i in range(5):
#创建子进程,放到进程池统一管理
#apply_async(target,args)
p.apply_async(run,args=(i,))
#必须在join之前调用close,调用close之后将不能再添加新的进程
p.close()
#进程池对象调用join,触发进程的启动,父进程会等待所有的子进程执行结束之后才结束
p.join()
print("父进程结束")
"""
如果要使用进程池创建多个进程,必须调用close和join
"""
#需求:实现文件内容的拷贝
import os,time
#实现文件的拷贝
def copyfile(rpath,wpath):
fr = open(rpath,"rb")
fw = open(wpath,"wb")
content = fr.read()
fw.write(content)
fr.close()
fw.close()
if __name__ == "__main__":
path = r"C:\Users\Administrator\Desktop\text1"
topath = r"C:\Users\Administrator\Desktop\text2"
#获取path下面的所有的内容
filelist = os.listdir(path)
start = time.time()
for filename in filelist:
copyfile(os.path.join(path,filename),os.path.join(topath,filename))
end = time.time()
print("总耗时:%.2f" % (end - start))
# 需求:实现文件内容的拷贝
import os, time
from multiprocessing import Pool
# 实现文件的拷贝
def copyfile(rpath, wpath):
fr = open(rpath, "rb")
fw = open(wpath, "wb")
content = fr.read()
fw.write(content)
fr.close()
fw.close()
if __name__ == "__main__":
path = r"C:\Users\Administrator\Desktop\text1"
topath = r"C:\Users\Administrator\Desktop\text3"
# 获取path下面的所有的内容
filelist = os.listdir(path)
p = Pool(4)
start = time.time()
for filename in filelist:
#copyfile(os.path.join(path, filename), os.path.join(topath, filename))
p.apply_async(copyfile,args=(os.path.join(path, filename),os.path.join(topath, filename)))
p.close()
p.join()
end = time.time()
print("总耗时:%.2f" % (end - start))
封装进程对象
from multiprocessing import Process
import os,time
#1.自定义一个类,继承自Process
class CustomProcess(Process):
#2.书写构造函数,定义实例属性,表示当前进程的名称
def __init__(self,name):
#3.调用服了你的构造函数
Process.__init__(self)
self.name = name
#4.定义成员函数,任务的执行函数
def run(self):
print("子进程(%s-%s)启动" % (self.name,os.getpid()))
#子进程的功能
time.sleep(2)
print("子进程(%s-%s)结束" % (self.name, os.getpid()))
if __name__ == "__main__":
print("父进程启动")
p = CustomProcess("1111")
p.start()
p.join()
print("父进程结束")
进程之间的通信
Process之间是需要相互通信的,操作系统提供了很多的办法实现进程间的通信,Python中的multiprocessing包装了底层的机制,提供了Queue[队列],
#需求:在主进程中创建两个子进程,一个进程用来向队列中写数据,另外一个进程用来从队列中读数据
from multiprocessing import Process,Queue
import os,time,random
#1.写数据的任务
def write(queue):
print("进程%s开始" % (os.getpid()))
print("开始写入")
for value in ["A","B","C"]:
print("add %s to queue" % (value))
#向队列中添加数据
queue.put(value)
#稍休息片刻,接着加
time.sleep(random.random())
print("进程%s结束" % (os.getpid()))
#2.读数据的任务
def read(queue):
print("进程%s开始" % (os.getpid()))
print("开始读取")
n = 0
while n < 3:
#从队列中获取数据
value = queue.get(True)
print("get %s from queue" % (value))
n += 1
print("进程%s结束" % (os.getpid()))
#3.在主进程中分别创建子进程,执行相应的任务
if __name__ == "__main__":
print("父进程启动")
#3.1创建队列对象,并传参给每个进程
q = Queue()
#3.2创建进程对象,
pr = Process(target=read,args=(q,))
pw = Process(target=write,args=(q,))
#3.3启动子进程,让进行数据的读写
pw.start()
pr.start()
#3.4设置,让所有子进程结束之后,才结束父进程
pr.join()
pw.join()
print("父进程结束")
二.线程
概念
是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务
注意:一个进程至少需要一个线程,否则该进程没有意义.
线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是在cpu之间进行快速的切换,[每个线程都有争抢时间片的机会,谁抢到时间片,则执行对应的线程,剩下的线程都会被挂起]
例如: 打开网易云音乐----->启动一个进程
播放歌曲和刷新歌词------->启动了两个线程
进程和线程的关系
a.一个程序启动之后至少有一个进程
b.一个进程可以包含多个线程,但是至少需要一个线程,否则该进程没有意义
c.进程之间的资源不共享,线程之间的资源是共享的
d.系统创建进程需要为该进程重新分配系统资源,二创建线程容易的多,因此使用多线程实现多任务比多进程实现效率更高
创建线程
_thread模块,提供了低级别的,原始的线程[低级别并不是不好,知识功能有限,底层采用的c语言]
threading模块:高级模块,对_thread进行了封装,提供了_thread没有的功能
import threading,time
#2.定义子线程需要执行的任务
def run(num):
print("子线程%s启动" % (threading.current_thread().name))
time.sleep(2)
print("num=%d" % (num))
print("子线程%s结束" % (threading.current_thread().name))
if __name__ == "__main__":
#1.任何进程默认会启动一个线程,称为主线程,在主线程中启动其他的子线程
#threading.current_thread()返回一个当前正在执行的线程对象
#threading.current_thread().name获取当前正在执行的线程的名称,主线程默认的名称为MainThread
print("主线程%s启动" % (threading.current_thread().name))
#3.创建子线程
#threading.Thread(target,name,args),target需要执行的任务,name表示线程的名称,args执行的任务函数的参数
#3.1任务函数没有参数
#注意:name也可以省略,每个子线程默认有名称,根据线程被创建的顺序,为Thread-1,Thread-2.....
#t1 = threading.Thread(target=run,name="11111")
#3.2任务函数有参数
t1 = threading.Thread(target=run,args=(10,))
#4.手动启动子线程
t1.start()
#5.想让主线程等待子线程执行结束之后才结束,设置join
t1.join()
print("主线程%s结束" % (threading.current_thread().name))
线程中的数据共享
问题:在多线程中,全局变量被所有线程所共享,所以,任何一个全局变量可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时修改同一个变量,容易将数据改乱
#需求:使用一个全局变量代表银行卡余额,两个线程都可以访问银行卡的余额
from threading import Thread
balance = 0
def change(n):
global balance
balance = balance + n
balance = balance - n
#子线程的任务
def run(n):
for _ in range(1000000):
change(n)
if __name__ == "__main__":
t1 = Thread(target=run,args=(5,))
t2 = Thread(target=run,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
#理论上,balance最终的结果为0
print(balance)
"""
问题:
先加后减,理论上结果为0,但是,如果访问的次数变多,则结果不一定为0
原因:
使用多线程容易出现的临界资源问题
高级语言中一条语句分步执行的,所以balance = balance + n在内存中执行的顺序是:
a.计算balance + n,将结果存储到一个临时变量中 x = balance + n
b.将临时变量的值赋值给balance balance = x
1.正常情况下
t1:x = balance + n #5
t1:balance = x #balance = 5
t1:x = balance - n #0
t1:balance = x #0
t2:x = balance + n #8
t2:balance = x #balance = 8
t2:x = balance - n #0
t2:balance = x #0
2.实际情况下:
t1:x1 = balance + n #x1 = 5
t2:x2 = balance + n #x2 = 8
t2: balance = x2 #balance = 8
t1:balance = x1 #balance = 5
t1:x1 = balance - n #x1 = 0
t1:balance = x1 #balance = 0
t2:x2 = balance - n #x2 = -8
t2:balance = x2 #balance = -8
总结:修改balance需要多条语句,执行多条语句的时候,线程随时可能会被中断,从而导致多个线程将一个变量修改乱了
解决:上锁【给多线程中的临界资源上一把锁,当一个线程访问的时候,其他的线程则必须在锁外面进行等待,直到锁被释放之后,
其他线程才有再次争抢时间片的机会】
"""
线程锁
from threading import Thread,Lock
balance = 0
#创建一个锁对象
lock = Lock()
def change(n):
global balance
balance = balance + n
balance = balance - n
#子线程的任务
def run(n):
for _ in range(1000000):
#获取锁
lock.acquire()
try:
# 临界资源
change(n)
finally:
#释放锁
lock.release()
if __name__ == "__main__":
t1 = Thread(target=run,args=(5,))
t2 = Thread(target=run,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
#理论上,balance最终的结果为0
print(balance)
from threading import Thread,Lock
balance = 0
#创建一个锁对象
lock = Lock()
def change(n):
global balance
balance = balance + n
balance = balance - n
#子线程的任务
def run(n):
for _ in range(1000000):
#同样表示获取锁,但是,不用手动释放,当临界资源的代码执行完毕之后,锁会被自动释放
with lock:
# 临界资源
change(n)
if __name__ == "__main__":
t1 = Thread(target=run,args=(5,))
t2 = Thread(target=run,args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
#理论上,balance最终的结果为0
print(balance)