多任务
附加多任务,多线程笔记
多任务是同时进行,就像歌手同时唱歌和跳舞,同步进行
windows liunx都是多任务 ,同一时刻进行多项操作
单核CPU之所以能多任务,是因为把每个程序执行时间打碎了,这种算法是时间片轮转,雨露均沾;还有一种算法,优先级调度
四核CPU就是4个单核CPU,
并发:一个CPU 使用时间片轮转的多个任务 -看起来是并发,执行程序大于核心
并行:多个CPU 每个程序占用一个单核CPU
多任务用fork
使用fork,就会创建一个进程,程序运行后就是进程,进程是有生命的
使用fork,需要先导入os模块
用变量 = os.fork()
os.for(),返回两个值
主(父)进程 生成 子进程
系统给主进程返回值>0,给子进程返回值 ==0
每次打印的顺序不一定,要看系统的调度算法
多任务多线程
threading是python程序中创建线程所需的模块
创建一个Thread类的实例对象 ---- 构造线程运行所需要的资源
thread 线程 process进程 program程序
target 目标 该参数用以指定线程在执行时的函数代码
如果想要给子线程传递参数 使用Thread类的args参数(元祖)
args是传递给线程函数的的参数元组 将该参数元祖拆包之后的结果赋值给函数形参 thd = threading.Thread(target=say_hello, args=(i,)) # say_hello(i)
thd1 = threading.Thread(target=dance)
thd2 = threading.Thread(target=drink)
多任务 – 就是同时进行多个任务
线程是实现多任务的一种方式
一个进程里默认有一个线程,叫主线程;通过主线程创建的线程叫子线程
如果在进程中主线程创建了子线程,需要子线程结束后,主线程才会结束
查看当前进程中的线程数量 enumerate()线程列表
print(threading.enumerate())` ### 实例对象.join() 表示的意思就是 主线程 阻塞等待这个实例对象标识的线程结束
thd1.join()
多个线程对同一个数据进行读取,就会产生数据混乱
互斥锁 有我没你,有你没我
1 多任务
并行
并发
2 父线程与子线程
一个进程中默认只有一个线程 — 主线程
3 创建子线程的两种方法
使用Thread类 实例对象
class threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
常用参数
target指定子线程运行的函数名
args执行子线程的函数代码在运行时 的参数
thd = threading.Thread()
使用Thread子类 实例对象
实现其中的run()方法
thd.start()方法 只是启动子线程的创建和执行
4 主线程等待子线程退出
默认情况下父线程会等待所有子线程退出
如果需要在子线程执行完成后 还需要执行一些代码(比如检测子线程+10000次之后的运算结果) 需要在主线程中使用 Thread实例对象.join()
5 同一个进程内部的多个线程 共享全局变量
6 多线程同步
互斥锁保证在任何一个时间点只有 一个 任务可以获取并占有 锁资源
创建锁资源 mutex = threading.Lock()
获取锁资源 mutex.require()
释放锁资源 mutex.release()
7 互斥锁的优缺点
优点
保证多任务修改共享数据时能够同步 使运算结果正确
缺点
丧失多任务优势
容易造成死锁问题
8 死锁问题
任务在获取锁时造成 一直在等待(死等) 锁资源 而不能成功获取
原因:
多种锁资源分配不当 – 银行家算法可以解决该问题
没有正确释放 –
用户规范 用完一定要关
权宜之计就是在获取锁的时候 使用非阻塞 或者 阻塞+超时等待 方式获取锁
9 acquire()方法功能说明
acquire(blocking=True, timeout=-1) 函数作用 申请锁资源 参数含义 blocking参数表示是否阻塞 True表示阻塞 False表示非阻塞(不阻塞等待) timeoute参数表示阻塞等待的最长时间 超时则放弃等待 -1表示死等 如果是一个正数 代表等待这么秒
返回值 表示是否成功获取到锁资源 True表示成功获取 False表示获取失败
进程
进程状态切换
就绪 除CPU资源之外的所有资源已经分配
在时间片用完的
等待 在运行的代码中有需要等待的条件(input sleep )阻塞等待
os.getpid() 获取当前进程的ID
os.getppid() 获取当前进程的父进程PID
pro.jion() 使用jion回收子进程的资源 在进程中,父进程是不会等待子进程结束,在线程中,父线程会等待子线程结束
进程能间通信-Queue 对列 存储数据–类似列表
创建队列:
multiprocessing.Queue() 该参数表示队列长度 往队列中放入数据
q.put(数据) 如果队列已满,则会默认阻塞等待 从队列中取出队列数据
q.get() 如果没有数据,默认阻塞等待
队列中的长度
q.size()
在主进程中 阻塞等待子进程运行完成退出 后再继续运行 – 回收子进程的资源
pro.join()
告诉操作 直接结束子进程 而不用等待他自动退出 —- 操作系统收到后会尽快处理 因此有一定时间差
pro.terminate()
进程:一个程序可以有多个进程,是独立单位
线程:一个进程可以有多个线程,
进程创建:
.Process类的实例对象
.start() -- 启动子进程的创建和执行
.is_alive() -- 判断子进程的运行状态
.terminate() -- 终止进程(不会立即子进程退出---时间差)
.join() --- 阻塞等待子进程退出 才会继续往下执行 参数timetout表示超时等待的时间
Process的参数
target指定子进程在运行时的函数
args 匿名参数 元组
kwargs 命名参数 字典
.pid 一定要在进程创建后再获取
.name Process-N
进程间通信-Queue队列
创建
q = multiprocessing.Queue() 参数表示队列长度 -- 数据条数 往里面放数据
q.put(data) 取出数据
q.get() 判断满 # 判断队列是否是满的 如果满的 返回True 否则返回false
q.full() 判断空 # 判断队列是否为空 如果是空的返回True 否则返回False
q.empty() 获取队列中的数据数量 mac会崩溃
q.qsize()
put(obj[, block[, timeout]]) obj表示数据对象 block表示是否阻塞等待 True表示阻塞等待 False表示不阻塞 不等待 timeout 表示如果等待 等待最长的时间
get([block[, timeout]]) block表示是否阻塞等待 True表示阻塞等待 False表示不阻塞 不等待 timeout 表示如果等待 等待最长的时间
进程线程总结
进程是操作系统进行资源
分配的基本单位
线程是操作系统进行资源
调度的基本单位
进程池:
apply 阻塞的任务添加方式 – 添加任务后 还需要等待任务执行完成 才会继续往运行
apply_async 非阻塞的任务添加方式 – 只管添加任务 不管其他
进程池的工作进程之间通信 需要使用 multiprocessing.Manager().Queue()
进程池需要Mnanger().Queueu,进行数据共享
import multiprocessing
import time
def worker1():
"""这个是一个任务"""
for i in range(3):
print("这是进程池中的一个任务1")
time.sleep(1)
def worker2():
"""这个是一个任务"""
for i in range(3):
print("这是进程池中的一个任务2")
time.sleep(1)
def main():
#创建进程池
pool = multiprocessing.Pool(3)
#使用进程池中的进程 -- 往进程池中添加一个任务 等待任务执行完成之后 才会继续往下执行
#func参数指定进程运行的函数代码 阻塞的任务添加方式
pool.apply(func=worker1)
pool.apply(func=worker2)
# 只管添加任务 不管任务执行 -- 非阻塞
pool.apply_async(func=worker1)
pool.apply_async(func=worker2)
# 关闭进程池 --- 不再接收新的任务
pool.close()
# 阻塞等待所有的进程任务运行结束 在join之前一定要close
pool.join()
print("所有任务执行完成")
if __name__ == '__main__':
main()
迭代器
迭代 —遍历访问
可迭代对象 能用for ..in 遍历的就是可迭代对象
iterable 可迭代的意思
isinstance(mlist, Iterable) 判断是否可迭代对象 ;判断左边的对象是否可迭代类型
print(isinstance([],list))
iterable 可迭代的
iterator 迭代器
可迭代对象
的本质就是提供一个迭代器 用以迭代访问自己当中的数据
iter
迭代器的作用
通过next()操作不断获取可迭代对象中的下一个位置的数据 每一次访问完成 自动指向下一个位置
next
迭代器本身也是一个可迭代对象 所以 迭代器自己需要实现__iter__方法 返回自己self
data = [1,2,3,4,5]
instance 实例对象 — 判断左边的对象是否是可迭代类型的实例对象
print(isinstance(mlist, Iterable)) ### 1 使用iter(可迭代对象)获取可迭代对象提供的迭代器
data_iter = iter(data)
while True: ### 2 使用next(迭代器)遍历可迭代对象中的数据 如果已经遍历完成 将抛出StopIteration
# try:
# i = next(data_iter)
# except StopIteration as e:
# print("迭代完成")
# break
# else:
# print(i)
# print(isinstance([], list))
# for i in mlist:
# print(i) #### 如何判断可迭代对象 ### 迭代器的作用 for循环的实现过程
迭代实现斐波那契:
class Fbiter:
def __init__(self,num):
self.num = num
self.index = 0
self.nub1 = 0
self.nub2 = 1
def __iter__(self):
return self
def __next__(self):
if self.index < self.num:
self.index +=1
data = self.nub1
self.nub1,self.nub2 = self.nub2,self.nub1+self.nub2
return data
else:
raise StopIteration
if __name__ == '__main__':
fb = Fbiter(10)
for i in fb:
print(i)
生成器
生成器:
是一种吗特殊的迭代器,简单一点的生成器表达式
yield关键字的作用:
yield 暂时挂起当前函数 把 yield后面表达式的值 返回给调拥生成器的地方
恢复当前函数的执行,从yiele这句开始执行
挂起当前函数 将后面表达式的值 返回到调用生成器的地方
接收数据 并唤醒当前函数 并且紧接着上次运行的地址继续执行
import time
def worker1():
while True:
print("in worker1")
yield
time.sleep(0.5)
# F7 步进 进入函数调用 F8 步过
def worker2():
while True:
print("in worker2")
yield # 挂起当前函数 切换到调用该生成器对象的地方
time.sleep(0.5)
if __name__ == '__main__':
# 调用生成器函数 产生生成器对象
w1 = worker1()
w2 = worker2()
while True:
next(w1)
next(w2)
唤醒生成器的两种方式:
生成器.send(data)
next(生成器) ===生成器.send(data)
在一般情况下, send 和 next 可以混用,但是第一次调拥生成器对象时,必须使用next
def fib(n):
"""带有yield关键字的函数已经不是普通函数 而是生成器函数"""
current = 0
num1, num2 = 0, 1
while current < n:
num = num1
num1, num2 = num2, num1+num2
current += 1
# 两层含义 1 暂时挂起当前函数 把yield后表达式的值 返回给调用生成器的地方
# 2 next() send()恢复当前函数的执行 从yield这条语句下一个位置开始
yield num
# 调用生成器函数 产生生成器对象
f = fib(10)
# 迭代访问生成器对象 进行执行
# for i in f:
# # i = next(f)
# print(i)
while True:
try:
num = next(f)
except StopIteration as e:
print("运行完成" + e.value)
break
else:
print(num)
协成 —-切换
monkey.patch_all() # 破解阻塞
生成器.send(“数据”)
next(生成器) === 生成器.send(None)
在第一次调用生成器对象的是 必须使用next()
在后续的情况下 send和next可以混用
协程 – 切换
gevent提供了能够自动切换任务的功能 但是需要破解
gevent.spawn() 启动协程的创建和运行
from gevent import monkey
monkey.patch_all() # 破解哪些会阻塞任务的函数 time.sleep() recv recvfrom
import gevent
import time
def fun():
for i in range(3):
print(gevent.getcurrent())
# 默认阻塞当前任务
# 此时使用的time.sleep()函数已经被破解了 不会再阻塞当前函数了
# 这样这个协程在等待1秒之后 就能做其他事情 自动切换 到别人执行了
time.sleep(1)
# 创建并启动 协程
g1 = gevent.spawn(fun)
g2 = gevent.spawn(fun)
g3 = gevent.spawn(fun)
# 等待协程运行完成
# g1.join()
# g2.join()
# g3.join()
# joinall ([]) 表示阻塞等待所有的协程执行完成
gevent.joinall([g1, g2, g3])
path_all的作用就是 使一些默认会阻塞当前任务执行的函数变得不阻塞了 – 破解
建议 这一步在导入模块后第二行就使用
greenlet协成:
import greenlet
import time
def worker1():
while True:
print("in worker1")
# 切换到第二个协程执行
g2.switch()
time.sleep(0.5)
def worker2():
while True:
print("in worker2")
# 切换到第一个协程执行
g1.switch()
time.sleep(0.5)
# 创建两个协程对象 参数指定该协程执行的代码
g1 = greenlet.greenlet(worker1)
g2 = greenlet.greenlet(worker2)
g1.switch()