文章目录
一、进程和线程
1.概念
1.1多任务
程序的运行是CPU和内存协同工作的结果
操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统
问题1:什么是多任务?
就是操作系统可以同时运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务,至少同时有3个任务正在运行。还有很多任务悄悄地在后台同时运行着,只是桌面上没有显示而已
问题2:多核CPU已经非常普及了,但是,即使过去的单核CPU,也可以执行多任务。由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……这样反复执行下去。实际上,每个任务都是交替执行的,但是,表面上看,由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样【现在的电脑最起码都4核起】
并行:真正一起执行,任务数量小于cpu的核心数量【理想型】
并发:看上去一起执行,任务数量大于cpu的核心数量【现实型】
多核cpu实现多任务的原理:真正的并行执行多任务只能在多核cpu上实现,但是由于任务数量远远多于cpu的核心数量,所以操作系统也会自动把很多任务轮流调度到每个核心上执行
对于操作系统而言,一个任务就是一个进程【process】,一个操作系统可以开启多个进程
有的进程启动之后,有可能又需要处理多个子任务,这些子任务就是线程【thread】
1.2进程
是一个程序的运行状态和资源占用(内存,CPU)的描述
进程是程序的一个动态过程,它指的是从代码加载到执行完毕的一个完成过程
进程的特点:
a.独立性:不同的进程之间是独立的,相互之间资源不共享(举例:两个正在上课的教室有各自的财产,相互之间不共享)
b.动态性:进程在系统中不是静止不动的,而是在系统中一直活动的(举例:教室里一直在讲课)
c.并发性:多个进程可以在单个处理器上同时进行,且互不影响
多进程:一个操作系统可以运行多个应用程序
1.3线程
线程是进程的组成部分,一个进程可以有多个线程,每个线程去处理一个特定的子任务
线程的执行是抢占式的,多个线程在同一个进程中可以并发执行,其实就是CPU快速的在不同的线程之间切换,也就是说,当前运行的线程在任何时候都有可能被挂起,以便另外一个线程可以运行
例如:打开酷狗音乐 ————》这是一个进程
播放歌曲和刷新歌词————》这是两个线程(两个线程在同时交替执行,所以说是并发的)
一个进程中的不同线程之间是资源共享的
多线程:
在一个进程中,多个线程可以同时进行
多线程是实现了并发机制的一种有效手段,一个进程中可以包含多个线程,不同线程之间是可以资源共享的,同时运行可以提高程序的执行效率,可以同时完成多个工作
应用:一个浏览器可以同时下载多张图片和多个视频
一个服务器可以同时响应多个用户请求
进程和线程之间的区别和联系
a.一个程序启动之后,肯定会启动一个进程
b.一个进程启动之后,可能会启动多个线程,但是,该进程至少需要一个线程,否则这个进程是没有意义的
c.多个进程之间资源不共享,多个线程之间资源共享
d.系统创建进程需要为该进程重新分配系统资源,而创建线程容易很多,所以在实际开发中,使用多线程多于多进程
二、进程
1.单任务现象
# 任务2
def func():
print("11111111")
# 任务1
if __name__ == '__main__':
while True:
print("main~~~")
# 永远执行不到下面
func()
2.多进程实现多任务
2.1启动进程实现多任务
Unix/Linux操作系统提供了一个fork()
系统调用,fork()存在于os模块下【os.fork()】,但是,Windows没有fork
调用
由于Python是跨平台的,提供了一个跨平台的多进程支持。multiprocessing
模块就是跨平台版本的多进程模块
multiprocessing
模块提供了一个Process
类来代表一个进程对象
# 1.导入模块
from multiprocessing import Process
import os,time
# 子进程的任务
def func():
# os.getppid()表示获取当前进程的父进程的进程号
print(f"子进程启动,进程号:%s,对应的父进程为:%s" % (os.getpid(),os.getppid()))
while True:
print("11111111")
time.sleep(0.5)
def func1(a,b):
print(a + b)
print(f"子进程启动,进程号:%s,对应的父进程为:%s" % (os.getpid(),os.getppid()))
while True:
print("11111111")
time.sleep(0.5)
if __name__ == '__main__':
# 2.在__main__中执行的代码表示主进程/父进程
# 注意:一个程序启动,则表示一个父进程启动,系统会自动给该进程分配一个进程号,通过os.getpid()获取进程号
print(f"父进程启动,进程号:%s" % (os.getpid()))
# 3.在父进程中创建一个子进程
"""
Process(target,args)
target:当前进程需要执行的任务的函数名
args:表示任务函数的参数,值一定是一个元组
"""
# 注意:进程对象创建完毕之后,一定要手动启动
# a.任务函数没有参数
# p = Process(target=func)
# p.start()
# b.任务函数有参数
p = Process(target=func1,args=(23,10))
p.start()
# 主进程的任务
while True:
print("main~~~")
time.sleep(0.5)
2.2父子进程的先后顺序
from multiprocessing import Process
import os,time
def run():
print(f"子进程启动:%s" % (os.getpid()))
print('112431513531')
time.sleep(5)
print(f"子进程结束~~~~:%s" % (os.getpid()))
if __name__ == '__main__':
print(f"父进程启动:%s" % (os.getpid()))
p = Process(target=run)
p.start()
# 合并进程【将指定的子进程插队,会优先执行合并进程】
"""
在默认情况下,根据系统的调度,主进程有可能会早于子进程结束
但是当将子进程进行合并【join】之后,主进程会等待子进程执行完毕之后才结束
"""
p.join()
print('main~~~~~~')
time.sleep(0.5)
print(f"父进程结束~~~~~:%s" % (os.getpid()))
"""
join之前
父进程启动:63157
main~~~~~~
子进程启动:63158
112431513531
父进程结束~~~~~:63157
子进程结束~~~~:63158
join之后:
父进程启动:63138
子进程启动:63139
112431513531
子进程结束~~~~:63139
main~~~~~~
父进程结束~~~~~:63138
"""
2.3多个进程中的全局变量
from multiprocessing import Process
import os,time
# 需求:在子进程中修改全局变量,查看父进程中访问到的全局变量的值
num = 100
def run():
print(f"子进程启动~~~~")
global num
num += 50
print(f"子进程结束~~~~:{num}") # 150
if __name__ == '__main__':
print(f"父进程启动:%s" % (os.getpid()))
p = Process(target=run)
p.start()
# 优先执行子进程
p.join()
print(f"父进程结束~~~~~:%s" % (os.getpid()))
print(f"全局变量num={num}") # 100
"""
总结:
验证了进程之间是独立的,相互之间资源不共享
工作原理:在创建子进程对象的时候,会将所有全局数据进行备份,
每个进程都有自己的代码段,数据段和堆栈段,所以进程之间是独立的,资源不共享
"""
2.4启动大量子进程
如果要启动大量的子进程,可以用进程池的方式批量创建子进程
from multiprocessing import Process,Pool
import os,time
def func(n):
print(f"子进程{n}启动")
print("111111")
time.sleep(2)
print(f"子进程{n}结束~~~~~")
if __name__ == '__main__':
print(f"父进程启动")
# 创建多个子进程
# 方式一:Process
# p1 = Process(target=func)
# p1.start()
# p2 = Process(target=func)
# p2.start()
# p3 = Process(target=func)
# p3.start()
# 方式二:Pool
# 创建进程池的对象
# Pool(num):num表示可以同时执行的进程的数量,num省略,则默认值为当前操作系统的cpu核心数量
pool = Pool(2)
# 创建多个进程对象,通过进程池统一管理
for i in range(10):
pool.apply_async(func,args=(i,))
# 当子进程添加完毕后,需要关闭进程池,调用close之后表示向进程池中不能继续添加新的进程对象
pool.close()
# 使用进程池的时候,建议调用join,在此处会阻塞主进程,
# 当进程池中的所有子进程全部执行完毕,才会继续执行主进程中的任务
pool.join()
print(f"父进程结束~~~~")
2.5进程之间的通信
Process之间是需要通信的,操作系统提供了很多机制来实现进程间的通信,Python的multiprocessing模块包装了底层的机制,提供了Queue、Pipes等多种方式来交换数据
需求;在父进程中创建两个子进程,一个往Queue
里写数据,一个从Queue
里读数据
# 进程之间需要通信,无法直接通信,所以可以通过一个队列进行通信
from multiprocessing import Process,Queue
from time import sleep
# 需求:在父进程中创建两个子进程,一个进程向队列中写入数据,另一个进程从队列中获取数据
def write(queue):
print("写数据的进程启动")
for ch in "ABC":
print(f"put {ch} to queue")
# 向队列中添加数据
queue.put(ch)
sleep(1)
print("写数据的进程结束~~~~")
def read(queue):
print("读数据的进程启动")
for _ in range(3):
# 从队列中获取数据
value = queue.get(True)
print(f"get {value} from queue")
print("读数据的进程结束~~~~")
if __name__ == '__main__':
print("父进程启动")
# 创建一个队列的对象
queue = Queue()
# 创建进程的对象
write_process = Process(target=write,args=(queue,))
read_process = Process(target=read,args=(queue,))
write_process.start()
read_process.start()
write_process.join()
read_process.join()
print("父进程结束~~~")
三、线程
多任务可以由多进程完成,也可以由一个进程内的多线程完成
在一个进程的内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”叫做线程
线程通常叫做轻型的进程。线程是共享内存空间的并发执行的多任务,每一个线程都共享一个进程中的资源
线程是最小的执行单元,而进程由至少一个线程组成。如何调度进程和线程,完全由操作系统决定,程序自己不能决定什么时候执行,也不能决定执行多长时间
而且线程是操作系统直接支持的执行单元,因此,高级语言通常都内置多线程的支持,Python也不例外,并且,Python的线程是真正的Posix Thread,而不是模拟出来的线程
模块
1、_thread模块:提供了低级别的、原始的线程【低级并不是不好,只是功能比较有限,底层采用的是c语言封装的】
2、threading模块:高级模块,对_thre
ad进行了封装,并提供了_thread中没有的功能
绝大多数情况下,我们只需要使用threading这个高级模块
1.创建线程
创建并启动一个线程就是把一个函数传入并创建Thread
实例,然后调用start()
开始执行
"""
通过进程可以执行多任务,通过线程也可以执行多任务
创建进程:p = Process(target=xxx,args=(xx,xx..)) p.start() 进程号
创建线程:t = Thread(target=xxx,args=(xx,xx..)) t.start() 线程名称
"""
# 1.导入模块
# import multiprocessing # 进程模块
import threading,os,time # 线程模块
def run():
print(f"子线程启动:{threading.current_thread().name}")
time.sleep(2)
print(f"子线程结束~~~~:{threading.current_thread().name}")
def run1(num1,num2):
print(num1,num2)
print(f"子线程启动:{threading.current_thread().name}")
time.sleep(2)
print(f"子线程结束~~~~:{threading.current_thread().name}")
if __name__ == '__main__':
# 2.任何一个程序启动之后,会启动一个进程,
# 该进程被称为主进程,该进程会默认启动一个线程,被称为主线程
# threading.current_thread().name获取当前正在执行的线程的名称,主线程的名称为MainThread
print(f"主进程{os.getpid()}中的主线程:{threading.current_thread().name}")
# 3.创建子线程
"""
Thread(target,args,name)
target:当前线程需要执行的任务的函数名
args:表示任务函数的参数,值一定是一个元组
name:给线程命名
"""
# 注意:子线程默认的名称为Thread-1,Thread-2.。。。
# a.任务函数无参
# t = threading.Thread(target=run,name='hello')
# t.start()
# b.任务函数有参
t = threading.Thread(target=run1, name='hello',args=(34,6))
t.start()
# 和进程的用法类似,也可以合并线程,则该线程优先执行
t.join()
print(f"主进程{os.getpid()}中的主线程结束~~~~:{threading.current_thread().name}")
2.线程中的全局变量
import threading
# 假设银行卡余额
balance = 0
def change(n):
global balance
balance += n
balance -= n # 0
def run(m):
for _ in range(1000000):
change(m)
if __name__ == '__main__':
t1 = threading.Thread(target=run,args=(5,))
t2 = threading.Thread(target=run, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"balance={balance}") # 理论上结果为0
"""
多个进程之间是独立的,资源不共享,所以当一个进程修改全局变量,另一个进程访问到的结果没有影响
多个线程之间是资源共享的,所以当多个线程同时访问一个全局变量的时候,会导致全局变量的结果出现错误
原因:
balance += n-----》第一步:x = balance + n 第二步:balance = x
分析:
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
解决:
只需要保证一个线程抢到时间片之后,能够将所有的代码执行完毕,其他线程处于等待状态
实现:
给临界资源加锁,哪个线程抢到了时间片,则该线程持有锁 ,当临界资源的代码执行完毕,则再释放锁
"""
3.线程锁
import threading
# 创建一个锁对象
lock = threading.Lock()
# 假设银行卡余额
balance = 0
def change(n):
global balance
balance += n
balance -= n # 0
def run(m):
for _ in range(1000000):
# 获取锁
lock.acquire()
try:
change(m)
finally:
# 释放锁
lock.release()
if __name__ == '__main__':
t1 = threading.Thread(target=run,args=(5,))
t2 = threading.Thread(target=run, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"balance={balance}") # 理论上结果为0
import threading
# 创建一个锁对象
lock = threading.Lock()
# 假设银行卡余额
balance = 0
def change(n):
global balance
balance += n
balance -= n # 0
def run(m):
for _ in range(1000000):
# 进入with代码块,就会获取锁,当with代码块执行完毕,会自动释放锁
with lock:
change(m)
if __name__ == '__main__':
t1 = threading.Thread(target=run,args=(5,))
t2 = threading.Thread(target=run, args=(8,))
t1.start()
t2.start()
t1.join()
t2.join()
print(f"balance={balance}") # 理论上结果为0
4.定时线程
import threading
def run():
print("hello~~~~~")
if __name__ == '__main__':
print("主线程启动")
# Timer(seds,target)
t = threading.Timer(5,run)
t.start()
t.join()
print("主线程结束")
5.线程间的通信
import threading,time
def func():
# 创建一个事件的对象
event = threading.Event()
def run():
for i in range(5):
# wait():等待 clear():重置 notify():唤醒 notifyAll():唤醒所有
# 等待/阻塞,等待事件的触发
print("hello~~~~~~start")
event.wait()
print("hello~~~~~~end")
threading.Thread(target=run).start()
return event
e = func()
# 触发事件
for i in range(5):
time.sleep(1)
e.set()