概念:操作系统 同时运行多个任务。
方式:并行 并发
并行
任务数 小于CPU核数。任务是真的一起执行
并发
任务数 大于CPU核数。操作系统快速切换任务,造成看起来一起发生的效果。
多线程
import threadingimport timedef sing():"""唱歌5秒"""for i in range(5):print("---唱歌中---")time.sleep(1)def dange():"""跳舞5秒"""for i in range(5):print("---跳舞---")time.sleep(1)def main():t1 = threading.Thread(target=sing) # 函数名,不带括号t2 = threading.Thread(target=dange)t1.start()t2.start()if __name__ == '__main__':main()
程序中总有一个主线程,从上到下执行代码。当主线程遇到t.start()时,才分出一个子线程。
多线程之间共享全局变量
给线程传递参数
def test(num):print(num)t = threading.Thread(target=test, args=(100, )) # 100是要传给test函数的参数,必须写成元组形式t.start()
互斥锁
"""由于 线程共享全局变量,所以不同线程 操作 同一个全局变量时,为了解决资源竞争问题,所以要用到互斥锁。"""import threadingimport timeg_num = 0def test1(num):global g_num# ↓↓ 若mutex处于未锁状态,则此时上锁成功;若mutex处于锁定状态,则此时阻塞,直至mutex恢复解锁状态mutex.acquire()for i in range(num):g_num += 1mutex.release()# ↑↑ 解锁print("test1结束后g_num的值为%d" % g_num)def test2(num):global g_nummutex.acquire()for i in range(num):g_num += 1mutex.release()print("test2结束后g_num的值为%d" % g_num)# 创建一个互斥锁,默认是未上锁状态mutex = threading.Lock()def main():t1 = threading.Thread(target=test1, args=(1000000,))t2 = threading.Thread(target=test1, args=(1000000,))t1.start()t2.start()time.sleep(3)print("最终g_num的值为%d" % g_num)if __name__ == '__main__':main()
上锁部分的代码越少越好
锁内的代码耗时越长,其他用到锁的地方就需要等待得越长,所以将必要的操作锁起来就可以:
def test1(num):global g_numfor i in range(num):mutex.acquire()g_num += 1mutex.release()# ↑↑ 解锁print("test1结束后g_num的值为%d" % g_num)
进程
程序:xx.py、QQ.exe这种类似的是程序,它是静态的
进程:一个程序运行起来后,代码+用到的资源 称之为进程。
进程是操作系统 分配资源 的基本单位
代码示例(几乎和threading模块一样)
import multiprocessingimport timedef test1():while True:print('1......')time.sleep(1)def test2():while True:print('2......')time.sleep(1)def main():p1 = multiprocessing.Process(target=test1)p2 = multiprocessing.Process(target=test2)p1.start()p2.start()if __name__ == '__main__':main()
当代码运行到p1.start()时,相当于又增加了一份代码,子进程在新的代码中运行;p2.start()时又多一个子进程。这时程序中就有了1个主进程+2个子进程
代码+资源=进程
先有进程,再有线程
进程间通信
队列:Queue # 先进先出
import multiprocessingq = multiprocessing.Queue(3) # 数据最多只能放3个# ↓↓ q可以放置任意类型的数据q.put(111)q.put('fasd')q.put([1, 23, '23'])q.put(1) # 如果队列满了,则会一直等待q.get()q.get()q.get()q.empty() # q是否为空q.full() # q是否满q.get() # 如果队列为空,那么会一直等待q.get_nowait() # 如果队列为空,那么会直接报错。一般和异常捕获结合使用q.put_nowait()
队列示例:
"""模拟 同时进行 下载数据 和 解析数据"""import multiprocessingdef download_from_web():"""模拟下载数据"""data = [1, 2, 3, 4]print("已经下载完毕数据")def analysis_data():"""模拟分析数据"""print("进行分析数据")def main():p1 = multiprocessing.Process(target=download_from_web)p2 = multiprocessing.Process(target=analysis_data)p1.start()p2.start()if __name__ == '__main__':main()
由于进程之间数据不共享,所以,上面的代码要配合Queue来进行同时下载、解析。改为如下代码:
def download_from_web(q):"""模拟下载数据"""data = [1, 2, 3, 4]# ↓↓ 向队列q中放数据for i in data:q.put(i)print("已经下载完毕数据")def analysis_data(q):"""模拟分析数据"""data_list = list()# 从队列中获取数据while True:data = q.get()data_list.append(data)if q.empty():breakprint("进行分析数据:", data_list)def main():q = multiprocessing.Queue() # 如果不填数字,默认最大p1 = multiprocessing.Process(target=download_from_web, args=(q,))p2 = multiprocessing.Process(target=analysis_data, args=(q,)) # 通过传参将q传入p1.start()p2.start()
协程
迭代器
可迭代对象
from collections import Iterablea = [1, 2, 3, 4, 5]isinstance(a, Iterable) # 结果为True# 只要一个对象,属于Iterable的子类,那么这个对象就是可迭代对象# 自造可迭代对象class Classmate:def __iter__(self):passc = Classmate()print("c是否为可迭代对象?", isinstance(c, Iterable)) # 结果为True
所以,只要一个对象中有__iter__方法,那么它就是可迭代对象。但此时,依旧不能用for循环来遍历它。
for 循环遍历的条件
这个对象是可迭代对象(拥有
__iter__方法)它的
__iter__方法返回一个对象的引用。这个对象必须满足:- 这个对象是可迭代对象(拥有
__iter__方法) - 这个对象拥有
__next__方法
- 这个对象是可迭代对象(拥有
代码示例:
"""如何使它能够被for循环迭代??"""# 要求1:这个对象必须有__iter__方法# 要求2:它的__iter__方法必须返回 一个对象的引用。# 这个对象的要求是:1、有__iter__方法;2、有__next__方法。class Classmate:def __iter__(self):return ClassIterator() # 返回一个对象的引用# ↑↑ 这个对象ClassIterator() 它的内部包含__iter__和__next__方法class ClassIterator:def __iter__(self):passdef __next__(self):passc = Classmate()
此时,可迭代对象c ,就可以被for循环遍历(理论上)。
对象c的__iter__方法的返回值:ClassIterator(),就是一个迭代器
当c被for循环时,就相当于调用ClassIterator()这个对象的__next__方法。__next__返回什么,for循环就得到什么。
简化迭代器
上例中,ClassIterator()对象只需要满足两个条件就可以作为迭代器,那么可以直接在原来的可迭代对象中新增__next__方法,让其既是可迭代对象,又是迭代器:
import timefrom collections import Iterable, Iteratorclass Classmate:def __init__(self):self.names = list()self.current_num = 0def __iter__(self):return selfdef __next__(self):if self.current_num < len(self.names):ret = self.names[self.current_num]self.current_num += 1return retelse:raise StopIterationclassmate = Classmate()classmate.add('小明')classmate.add('小花')classmate.add('小龙')for name in classmate:print(name)time.sleep(1)
生成器
生成器是一种特殊的迭代器
生成器实现方式
- 列表生成式的方括号
[]换成圆括号()```python nums = [x for x in range(10)] print(nums) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
nums = (x for x in range(10))
print(nums) #
-函数中有`yield````pythondef fib(count):a, b = 0, 1current_count = 0while current_count < count:# print(a)yield a # 把要使用时的print换成yield,这个函数被调用时,就获得了一个生成器a, b = b, a + bcurrent_count += 1obj = fib(10)for i in obj:print(i)print('------------------------------------------')# 如果紧接着用for循环,会发现什么都打印不出来# for j in obj:# print(j)# 如果用next调用,会发现直接报错。(因为生成器中的值都已经被第一个for循环打印出来了)print(next(obj))# 如果 注释掉上面的for循环,这里就可以打印。且每次打印的值都是下一个的值。print(next(obj))print(next(obj))如果向多次使用这个生成器,则 再创建一个生成器对象即可。obj2 = fib(20)print(next(obj2))
分析:for循环内,函数执行到yield a时 会暂停,把a直接打印出来,当for循环再次遍历obj时,函数会接着上次yield a的地方继续执行。
使用生成器完成多任务——协程
import timedef task_1():while True:print("---任务1执行中---")time.sleep(0.2)yield # 加上yield,让这个任务函数变成生成器def task_2():while True:print("---任务2执行中---")time.sleep(0.2)yielddef main():t1 = task_1()t2 = task_2()while True:next(t1) # 利用生成器的方式切换任务,这就是 协程next(t2)if __name__ == '__main__':main()
协程方式使用的模块——greenlet、gevent
简单总结
- 进程是资源分配的单位
- 线程是操作系统调度的单位
- 进程切换需要的资源很大,效率很低
- 线程切换需要的资源一般,效率一般。(不考虑GIL)
- 协程切换任务资源很小,效率高
- 多进程、多线程根据CPU核数不一样,可能是并行的,但协程是在一个线程中,所以是并发
