ps:python开发学习笔记(5)在写的时候发生了丢失。。。。。之后再补吧。今天的多任务学到了生成器,博客分成两部分进行编写。
多任务
多任务:操作系统可以同时运行多个任务。
单核cpu,时间片轮转。并发:假多任务
多核cpu,并行:真多任务
1.线程
1>语法:
使用threading模块
import threading
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start()
t2.start()
sing和dance是函数名,这里传递的是引用。主线程在main函数中,子线程分别去执行sing和dance函数。
2>查看线程数量
threading.enumerate() 查看这个函数返回的列表里有几个元素,就有几个线程。可以用len函数。
如果创建Thread时执行的函数运行结束,那么意味着这个子线程结束。
主线程不能提前结束,主线程结束的话,整个程序都会结束。
主线程会等待所有子线程都结束,才会结束。
当调用start()方法后才会创建进程。用Thread()时,不会创建子线程。
多线程时,无法确定谁先执行,谁后执行。
3>线程-注意点
a。如果定义了Thread类的子类,子类中必须定义run方法,因为子类对象调用start()时会自动调用run()方法。
b。多个线程之间是共享全局变量的。函数中全局变量的指向不发生改变时,不需要用global声明。如,list是全局变量,在函数中调用list.append(a)时,不需要声明global,list的值也会发生改变。
c。Thread 参数
t1 = threading.Thread(target=test1, args=(g_nums,))
target指定将来这个线程去哪个函数执行代码,这里指定的是test1()函数
args指定将来调用函数的时候传递什么数据过去。要求调用的函数需要参数传递。args传递的是元组。
d。共享全局变量问题
资源竞争。如果有全局变量,因为多线程是随机运行,在运行过程中,两个子线程的运行可能出现偏差,导致全局变量的更新不能同步,从而导致全局变量的值出错。
4>同步
同步就是协同步调,按预定的先后次序进行运行。
5>互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。
互斥锁状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类。
语法:
a。创建锁
mutex = threading.Lock()
b。锁定
mutex.acquire()
c。释放
mutex.release()
6>死锁、银行家算法
a。死锁
死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
避免死锁:程序设计时尽量避免(银行家算法);添加超时时间等。
b。银行家算法
逐个锁,但是约定好解锁的顺序。
7>udp多线程聊天室
将接收数据和发送数据写为两个函数,分别编入一个子线程
2.进程
1>概念
程序是静态的,进程是一个程序运行起来后,代码+用到的资源称之为进程,它是操作系统分配资源的基本单元。
进程的状态:新建;就绪态:运行的条件都已经就绪,正在等待cpu执行;运行态:cpu正在执行其功能;等待态:等待某些条件满足,例如一个程序sleep了,此时就处于等待态;死亡
2>创建进程
import multiprocessing
def main():
t1 = multiprocessing.Process(target=test1)
t2 = multiprocessing.Process(target=test2)
t1.start()
t2.start()
与多线程十分相似。
多进程不共享全局变量
3>进程、线程对比
进程,能够完成多任务,如在一台电脑上运行多个QQ
线程,一个进程里的多任务就是多线程。
线程不能够独立执行,必须依存在进程中。
进程理解为工厂的流水线,线程就是流水线上的工人。
优缺点:
线程执行开销小,但不利于资源的管理和保护;而进程正相反。
4>进程间通信Queue队列
两个进程间在内存中的同一块区域读取和存储数据,达到通信的目的。
a。语法
from multiprocessing import Queue
q = Queue(3) #初始化一个Queue对象,最多可接受三条put消息,不写参数时,存储空间为动态的。
q.put() #在开辟出来的内存区域存放数据
q.get() #将内存区域中的数据取出,取得顺序为队列顺序,先进先出
q.full() 返回True,则开辟的内存区域已满,反之,返回False。
q.empty() 返回True,则开辟的内存区域为空,反之,返回False。
b。作用
解耦:降低程序间耦合度,增加灵活度
c。步骤
创建一个队列
创建多个进程,将队列的引用当作实参进行传递,传递到函数中
在一个进程的函数中进行往队列中的写入
在另一个进程的函数中进行从队列中的读取
5>进程池Pool
需要创建的子进程数量不多时,可以利用multiprocessing中的Process动态生成多个进程。但如果需要上百甚至上千个目标时,就要用到Pool方法。
进程池可以重复利用,减少了资源的损耗。
进程池中进程的数量需要测试工程师大量的测试。
语法:
from multiprocessing import Pool
po = Pool(3) #定义一个进程池,最大进程数为3
for I in range(0,10):
po.apply_async(worker,(i,)) #Pool().apply_async(要调用的函数,(传递给目标的参数元组,)) 每次循环将会用空闲出来的子进程去调用目标 如果要调用的函数没有传入参数,那么后面的参数元组就没有必要写。
po.close() #关闭进程池,关闭后po不再接收新的请求
po.join() #等待po中所有子进程执行完成,必须放在close语句之后
3.协程
1>迭代器
迭代器是一个可以记住遍历的位置的对象,迭代器对象从集合的第一个元素开始访问,知道所有的元素被访问完结束。迭代器只能往前不能往后。
a。可迭代对象
from collections import Iterable
isinstace(a,Iterable) a写入一个变量,判断是否为可迭代对象
如果要让一个对象成为一个可迭代对象,可以使用for循环,那么必须实现__iter__方法,且这个方法必须返回一个对象,该对象的类中要有__iter__方法和__next__方法
for循环步骤:
for temp in xxxx:
首先,判断xxxx是否是可以迭代,对象类中是否有__iter__方法
接着,在第一步前提下,调用iter函数,得到xxxx对象的__iter__方法的返回值
最后,__iter__方法的返回值是一个迭代器,自动调用__next__方法
迭代器里有__iter__和__next__两个方法
__next__方法中输出对象的值,并有计数器
b。xrange()
range()生成的列表 的结果(python2.x)
xrange()生成列表的对象
python3中range()已经变为xrange()
类型转换也是应用到了迭代器
tuple(),list()
2>生成器
生成器是一种特殊的迭代器
nums = [x2 for x in range(10)] 列表
创建生成器方法1
nums = (x2 for x in range(10)) 生成器
创建生成器方法2
例子,斐波那契数列
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
函数中结果用yield语句,这个就不再是函数,而是一个生成器的模板。
如果调用有yield语句的生成器时,不是调用函数,而是创建一个生成器对象
利用for循环输出生成器对象
3>生成器send方式
用send方式传递的值为yield返回的值
4>yield实现多任务
import time
def task_1():
while True:
print(“----1----")
time.sleep(0.1)
yield
def task_2():
while True:
print(“-----2-----")
time.sleep(0.1)
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
next(t1)
next(t2)
if __name__ == “__main__”:
main()
t1和t2交替进行,实现协程
5>协程 greenlet模块
from greenlet import greenlet
import time
def test1():
while True:
print(“----A----“)
gr2.switch()
time.sleep(0.5)
def test2():
while True:
print(“---B---“)
gr1.switch()
time.sleep(0.5)
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch() #切换到gr1中运行
6>协程 gevent
语法:
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i) #getcurrent() 返回当前协程内存地址
g1 = gevent.spawn(f,5) #创建一个协程对象 greenlet
g2 = gevent.spawn(f,5)
g = gevent.spawn(f,5)
g1,join() #
g2.join()
g3.join()
gevent 遇到延时操作就切换
from gevent import monkey
monkey.patch_all()
这句语句可以把代码中的耗时操作换为gevent模块中耗时语句,从而实现协程多任务。
gevent.joinall() 参数为列表
将gevent.spawn()放入joinall参数表中,实现批量管理协程 join操作
7>进程、线程、协程对比
进程是资源分配的单位
线程是操作系统调度的单位
进程切换需要的资源最大,效率很低
线程切换需要的资源一般,效率一般(GIL不考虑的情况下)
协程切换任务资源最小,效率高
多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中所以是并发。