一:进程
进程:进程是对运行时程序的封装,是系统资源调度和分配的基本单位
1.认识进程
使用进程一般使用multiprocessing模块的Process类来创建。
import os
import time
from multiprocessing import Process
def run():
while True:
# os.getpid()获取当前进程的pid,os.getppid()获取父进程的pid
print('study is bad--',os.getpid(),'--',os.getppid())
time.sleep(1)
if __name__ == '__main__':
print('启动主进程')
# args 传参
p = Process(target=run, args=()) # 将需要进程执行的函数对象传进去
# 启动进程
p.start()
while True:
print('study is terrible',os.getpid())
time.sleep(1.2)
这里因为主线程的睡眠时间是1.2秒,子进程是1秒,所有有时候执行两次子进程才执行一次父进程
2.设置父子进程执行顺序
1中的执行的时候会出现一个问题,因为子进程没有加入到主进程,所有会出现主进程执行完了,子进程还在执行的情况,这个给一个处理的方法。
import os
import time
from multiprocessing import Process
def run():
# while True:
print('study is bad--',os.getpid(),'--',os.getppid())
time.sleep(3)
print('子进程结束')
if __name__ == '__main__':
print('启动主进程')
# args 传参
p = Process(target=run, args=())
# 启动进程
p.start()
# 父进程等待子进程结束
p.join()
print('父进程结束。')
这里join()方法,是使主进程在join的地方阻塞,只有当阻塞的子进程执行完,才会继续执行父进程
3.全局变量在多进程中不能共享
import os
import time
from multiprocessing import Process
num = 100
def run():
# while True:
print('study is bad--',os.getpid(),'--',os.getppid())
# time.sleep(3)
# 相当于新定义了一个 num = 100
global num
num += 1
print('func num %d'%id(num))
print('子进程结束',num)
if __name__ == '__main__':
print('启动主进程')
# args 传参
p = Process(target=run, args=())
# 启动进程
p.start()
# 每个进程都有自己的数据段,代码段,堆栈区,所以父子进程全局变量不能共用
# 父进程中 全局变量共用
# 父进程等待子进程结束
p.join()
print('num %d'%id(num))
print('父进程结束。',num)
这里想改变全局变量num的值,没有改变成功,是因为每一个进程在新建的时候,都会开辟一块内存,在这个空间会有自己的数据段,代码段,堆栈区,以及变量num会拷贝一个新的。所以两个num的存储位置不一致。
4.使用进程池
import os
import time
from multiprocessing import Process,Pool
import random
def run(num):
# while True:
start = time.time()
print(f'子线程{num},进程号{os.getpid()}启动')
time.sleep(random.choice([1,2,3]))
end = time.time()
print(f'子线程{num},进程号{os.getpid()}结束,耗时{end - start}')
if __name__ == '__main__':
print('启动主进程')
# Pool 默认为cpu核心数
pp = Pool(2)
for i in range(4):
# 创建进程 放入线程池
pp.apply_async(run,args=(i,))
pp.close()
pp.join()
print('父进程结束。')
使用进程池,新建进程池对象的时候,传入cpu数量(几核),一核一次只能执行一个任务,这创建了一个双核的进程池,但是有4个线程,所以只有执行完2个进程,才会继续执行另外2个进程
5.进程间通信
import os
import time
from multiprocessing import Process,Queue
def write(q):
print('写消息',os.getpid())
# time.sleep(5)
for item in ['A','B','C','D','E','F','G']:
time.sleep(0.5)
q.put(item)
print('写进程完成~~')
def read(q):
print('读进程',os.getpid())
while True:
time.sleep(0.5)
print(q.get(True))
print('读进程结束')
if __name__ == '__main__':
print('启动主进程')
q = Queue()
# args 传参
p1 = Process(target=write, args=(q,))
p2 = Process(target=read, args=(q,))
# 启动进程
p1.start()
p2.start()
print("主进程还在继续")
# 父进程等待子进程结束,阻塞
p1.join()
print('p1 结束')
# p2是死循环,手动结束
p2.terminate()
print('父进程结束。')
使用了队列(Queue)进行通信。
二:线程
线程:是进程的子任务,cpu调度和分配的基本单位,实现进程并发
1.认识线程
import threading
import time
def run():
print('打印')
time.sleep(2)
print('子线程%s结束'%(threading.current_thread().name))
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
print('启动主线程(%s)'%(threading.current_thread().name))
# 创建子线程
t = threading.Thread(target=run,name='rootThread')
t.start()
# 等待线程结束
t.join()
print('主线程%s结束'%(threading.current_thread().name))
2.线程间共享数据
import threading
import time
num = 0
def run(n):
global num
for i in range(1000000):
num += n
num -= n
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
print('启动主线程(%s)'%(threading.current_thread().name))
# 创建子线程
t1 = threading.Thread(target=run,args=(6,))
t2 = threading.Thread(target=run, args=(9,))
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print(num)
print('主线程%s结束'%(threading.current_thread().name))
期望的结果是0。但是两个线程操作同一个变量,没有限制顺序,同时进行,就有可能出问题
ps:多线程和多进程的区别,多进程中,每一个进程都享有自己独立的数据段,代码段,对战区间,进程间互不影响
多线程中,所有变量都可以被共享(一个进程内),线程存在数据共享导致数据混乱
3.使用线程锁解决数据混乱
import threading
import time
num = 0
# 锁对象
lock = threading.Lock()
def run(n):
global num
for i in range(1000000):
# 锁
# 阻止了多线程的并发执行,包含锁的部分代码只能以单线程模式执行,效率下降很多
# 线程可以有多个锁,不同线程持有不同的所,并试图获取其他的锁,有可能导致死锁。导致多个线程挂起,只能通过操作系统终止
# lock.acquire()
# try:
# num += n
# num -= n
# finally:
# # 释放锁
# lock.release()
# 同效果
with lock:
num += n
num -= n
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
print('启动主线程(%s)'%(threading.current_thread().name))
# 创建子线程
t1 = threading.Thread(target=run,args=(6,))
t2 = threading.Thread(target=run, args=(9,))
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print(num)
print('主线程%s结束'%(threading.current_thread().name))
使用了锁的机制,当访问共享的资源的时候,获得锁的线程先执行,等待执行完了,释放锁,给第二个线程,第二个线程才开始执行,同理多个线程等待的情景
4.线程局部变量(threadloacl)
import threading
import time
num = 0
# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()
def run(m,n):
m.x += n
m.x -= n
def func(n):
# 创建线程的局部变量x,每个线程都拥有互不影响名称相同的x,num这个变量只起到了赋值作用
local.x = num
for i in range(1000000):
run(local,n)
print('子线程%s,结果为%d'%(threading.current_thread().name,local.x))
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
print('启动主线程(%s)'%(threading.current_thread().name))
# 创建子线程
t1 = threading.Thread(target=func,args=(6,))
t2 = threading.Thread(target=func, args=(9,))
t1.start()
t2.start()
# 等待线程结束
t1.join()
t2.join()
print(num)
print('主线程%s结束'%(threading.current_thread().name))
5.使用信号量控制线程数量
import threading
import time
num = 0
# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()
# 一次只让2个线程执行
sem = threading.Semaphore(2)
def run():
with sem:
for i in range(5):
time.sleep(1)
print('%s--%d' % (threading.current_thread().name, i))
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
for i in range(5):
threading.Thread(target=run,).start()
# 等待线程结束
print(num)
print('主线程%s结束'%(threading.current_thread().name))
可以看出,设置了信号量(2)以后,只能执行指定数量的线程(2),只有当线程执行完以后,才会继续等待的线程
6.凑够一定数量才能执行
import threading
import time
num = 0
# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()
# 凑够4个才能继续
bar = threading.Barrier(4)
def run():
print('%s--start'%(threading.current_thread().name))
time.sleep(1)
# 凑够4个才会向下执行,不然就等待
bar.wait()
print('%s--end'%(threading.current_thread().name))
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
for i in range(5):
threading.Thread(target=run,).start()
# 等待线程结束
print(num)
print('主线程%s结束'%(threading.current_thread().name))
使用threading.Barrier设置线程数,只有线程的数量达到要求,才能使程序继续运行,并且等待下一组线程,不然就等待
7.延时执行
import threading
import time
from datetime import datetime
num = 0
# 锁对象
lock = threading.Lock()
# 线程局部变量
local = threading.local()
# 凑够4个才能继续
bar = threading.Barrier(4)
def run():
print('%s--start'%(threading.current_thread().name))
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
print(datetime.now())
t = threading.Timer(3,run)
t.start()
t.join()
print(datetime.now())
print('主线程%s结束'%(threading.current_thread().name))
使用threading.Timer设置等待时间,线程会先等待设定时间,之后才会运行子线程的部分
8.事件触发(threading.Event)
import threading
import time
num = 0
def func():
event = threading.Event()
def run():
for i in range(5):
# 阻塞 等待事件触发
event.wait()
# 重置
event.clear()
print('子线程%d'%i)
t = threading.Thread(target=run).start()
return event
if __name__ == '__main__':
# 任何进程默认启动一个线程,称之为主线程,主线程启动新的子线程
e = func()
for i in range(5):
time.sleep(1)
# 触发事件
e.set()
9.生产者,消费者
import random
import threading,queue,time
def product(i,q):
while True:
num = random.randint(0,1000000)
q.put(num)
print('生产者%d生产了%d数据放入了队列'%(i,num))
time.sleep(3)
# 任务完成
q.task_done()
def customer(i,q):
while True:
item = q.get()
if item is None:
break
print('消费者%d消费了%d数据'%(i,item))
time.sleep(3)
# 任务完成
q.task_done()
if __name__ == '__main__':
q = queue.Queue()
# 启动生产者
for i in range(4):
threading.Thread(target=product,args=(i,q)).start()
# 启动消费者
for i in range(4):
threading.Thread(target=customer,args=(i,q)).start()
10.线程调度
import threading,time
# 线程条件变量
cond = threading.Condition()
def run1():
with cond:
for i in range(0,10,2):
print(threading.current_thread().name,i)
time.sleep(1)
print('run1 start wait')
cond.wait()
print('run1 end wait')
print('run1 start notify')
cond.notify()
print('run1 end notify')
def run2():
with cond:
for i in range(1,11,2):
print(threading.current_thread().name,i)
time.sleep(1)
print('run2 start notify')
cond.notify()
time.sleep(1) # run1不会执行,run1被唤醒,还在等待锁
print('run2 end notify')
print('run2 start wait')
cond.wait()
print('run2 end wait')
if __name__ == '__main__':
threading.Thread(target=run1).start()
threading.Thread(target=run2).start()
使用了threading.Condition,只有满足条件之后才会执行,在使用wait()的时候,会释放锁,并进入挂起的状态,只有使用notify()方法取消挂去状态,进入等待锁执行的状态。等拿到锁了才能继续运行。
ps:
多任务
多进程,一个主进程,多个子进程,子进程故障,不影响其他进程,但是开销很大
多线程,效率相对于进程,要快很多,但是线程故障一个,可能导致整个进程崩溃
三:协程
协程:是一种比线程更加轻量级的存在,正如一个进程可以拥有多个线程一样,一个线程可以拥有多个协程。
1.了解协程
def A():
print("Astart")
B()
print('Aend')
def B():
print("Bstart")
C()
print('Bend')
def C():
print("Cstart")
print('Cend')
A()
-
子程序/函数:在所有语言中都是层级关系,比如A调用了B,然后B又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕
-
概述:看上去是子程序,但执行过程中,在子程序的内部可中断,然后转而执行别的子程序,不是函数调用。
执行结果类似于多线程的结果,但是只有一个线程,执行效率极高
2.协程原理
def run():
print(1)
yield 10
print(2)
yield 20
print(3)
yield 30
m = run()
print('*****')
print(next(m))
print(next(m))
print(next(m))
# print(next(m))
print('*****')
- 实现协程的关键在于使用yield,只有通过next才能向下执行,并在下个yield等待
- 协程:控制函数的阶段执行,节约线程或进程的切换
3.数据传输
def run():
data = ''
'''
第二次传值赋第一次的yield的那一行赋值,并在第二个yield等待
'''
print('start')
r = yield 'aa'
print('1 end')
print(1,r,data)
r = yield 'ee'
print(2,r,data)
r = yield data
print(3,r,data)
r = yield data
print(4,r,data)
m = run()
print('*****')
print(m.send(None))
print(m.send('a'))
print(m.send('b'))
print(m.send('c'))
print('*****')
执行方式:
- 带有yield的函数运行会返回一个生成器(generator),这里在调用send()方法才会直行到下一个yield的地方。
- send()会传入一个参数,从上到下,从右向左,直到达到yield就停止,并将yield右边的数据作为send()执行完的结果返回。
- 第一次send None的时候,达到yield就没有继续了,r实际上还没有赋值成功,只有第二次send a的时候r才赋值成功,这里是稍微需要花点时间理解一下的地方
4.协程实现生产者消费者
import random
def product(c):
c.send(None)
for i in range(5):
print('send start')
n = random.randint(0,100)
c.send(n)
print('send end %d'%n)
c.close()
def customer():
data = ''
while True:
n = yield data
if not n:
return
print('消费者消费了%d'%n)
data = '200'
c = customer()
product(c)
完结撒花~