概念:操作系统 同时运行多个任务。

方式:并行 并发

并行

任务数 小于CPU核数。任务是真的一起执行

并发

任务数 大于CPU核数。操作系统快速切换任务,造成看起来一起发生的效果。

多线程

  1. import threading
  2. import time
  3. def sing():
  4. """唱歌5秒"""
  5. for i in range(5):
  6. print("---唱歌中---")
  7. time.sleep(1)
  8. def dange():
  9. """跳舞5秒"""
  10. for i in range(5):
  11. print("---跳舞---")
  12. time.sleep(1)
  13. def main():
  14. t1 = threading.Thread(target=sing) # 函数名,不带括号
  15. t2 = threading.Thread(target=dange)
  16. t1.start()
  17. t2.start()
  18. if __name__ == '__main__':
  19. main()

程序中总有一个主线程,从上到下执行代码。当主线程遇到t.start()时,才分出一个子线程。

多线程之间共享全局变量

给线程传递参数

  1. def test(num):
  2. print(num)
  3. t = threading.Thread(target=test, args=(100, )) # 100是要传给test函数的参数,必须写成元组形式
  4. t.start()

互斥锁

  1. """
  2. 由于 线程共享全局变量,所以不同线程 操作 同一个全局变量时,
  3. 为了解决资源竞争问题,所以要用到互斥锁。
  4. """
  5. import threading
  6. import time
  7. g_num = 0
  8. def test1(num):
  9. global g_num
  10. # ↓↓ 若mutex处于未锁状态,则此时上锁成功;若mutex处于锁定状态,则此时阻塞,直至mutex恢复解锁状态
  11. mutex.acquire()
  12. for i in range(num):
  13. g_num += 1
  14. mutex.release()
  15. # ↑↑ 解锁
  16. print("test1结束后g_num的值为%d" % g_num)
  17. def test2(num):
  18. global g_num
  19. mutex.acquire()
  20. for i in range(num):
  21. g_num += 1
  22. mutex.release()
  23. print("test2结束后g_num的值为%d" % g_num)
  24. # 创建一个互斥锁,默认是未上锁状态
  25. mutex = threading.Lock()
  26. def main():
  27. t1 = threading.Thread(target=test1, args=(1000000,))
  28. t2 = threading.Thread(target=test1, args=(1000000,))
  29. t1.start()
  30. t2.start()
  31. time.sleep(3)
  32. print("最终g_num的值为%d" % g_num)
  33. if __name__ == '__main__':
  34. main()

上锁部分的代码越少越好

锁内的代码耗时越长,其他用到锁的地方就需要等待得越长,所以将必要的操作锁起来就可以:

  1. def test1(num):
  2. global g_num
  3. for i in range(num):
  4. mutex.acquire()
  5. g_num += 1
  6. mutex.release()
  7. # ↑↑ 解锁
  8. print("test1结束后g_num的值为%d" % g_num)

进程

程序:xx.pyQQ.exe这种类似的是程序,它是静态的

进程:一个程序运行起来后,代码+用到的资源 称之为进程。

进程是操作系统 分配资源 的基本单位

代码示例(几乎和threading模块一样)

  1. import multiprocessing
  2. import time
  3. def test1():
  4. while True:
  5. print('1......')
  6. time.sleep(1)
  7. def test2():
  8. while True:
  9. print('2......')
  10. time.sleep(1)
  11. def main():
  12. p1 = multiprocessing.Process(target=test1)
  13. p2 = multiprocessing.Process(target=test2)
  14. p1.start()
  15. p2.start()
  16. if __name__ == '__main__':
  17. main()

当代码运行到p1.start()时,相当于又增加了一份代码,子进程在新的代码中运行;p2.start()时又多一个子进程。这时程序中就有了1个主进程+2个子进程

代码+资源=进程

先有进程,再有线程

进程间通信

队列:Queue # 先进先出

  1. import multiprocessing
  2. q = multiprocessing.Queue(3) # 数据最多只能放3个
  3. # ↓↓ q可以放置任意类型的数据
  4. q.put(111)
  5. q.put('fasd')
  6. q.put([1, 23, '23'])
  7. q.put(1) # 如果队列满了,则会一直等待
  8. q.get()
  9. q.get()
  10. q.get()
  11. q.empty() # q是否为空
  12. q.full() # q是否满
  13. q.get() # 如果队列为空,那么会一直等待
  14. q.get_nowait() # 如果队列为空,那么会直接报错。一般和异常捕获结合使用
  15. q.put_nowait()

队列示例:

  1. """模拟 同时进行 下载数据 和 解析数据"""
  2. import multiprocessing
  3. def download_from_web():
  4. """模拟下载数据"""
  5. data = [1, 2, 3, 4]
  6. print("已经下载完毕数据")
  7. def analysis_data():
  8. """模拟分析数据"""
  9. print("进行分析数据")
  10. def main():
  11. p1 = multiprocessing.Process(target=download_from_web)
  12. p2 = multiprocessing.Process(target=analysis_data)
  13. p1.start()
  14. p2.start()
  15. if __name__ == '__main__':
  16. main()

由于进程之间数据不共享,所以,上面的代码要配合Queue来进行同时下载、解析。改为如下代码:

  1. def download_from_web(q):
  2. """模拟下载数据"""
  3. data = [1, 2, 3, 4]
  4. # ↓↓ 向队列q中放数据
  5. for i in data:
  6. q.put(i)
  7. print("已经下载完毕数据")
  8. def analysis_data(q):
  9. """模拟分析数据"""
  10. data_list = list()
  11. # 从队列中获取数据
  12. while True:
  13. data = q.get()
  14. data_list.append(data)
  15. if q.empty():
  16. break
  17. print("进行分析数据:", data_list)
  18. def main():
  19. q = multiprocessing.Queue() # 如果不填数字,默认最大
  20. p1 = multiprocessing.Process(target=download_from_web, args=(q,))
  21. p2 = multiprocessing.Process(target=analysis_data, args=(q,)) # 通过传参将q传入
  22. p1.start()
  23. p2.start()

协程

迭代器

可迭代对象
  1. from collections import Iterable
  2. a = [1, 2, 3, 4, 5]
  3. isinstance(a, Iterable) # 结果为True
  4. # 只要一个对象,属于Iterable的子类,那么这个对象就是可迭代对象
  5. # 自造可迭代对象
  6. class Classmate:
  7. def __iter__(self):
  8. pass
  9. c = Classmate()
  10. print("c是否为可迭代对象?", isinstance(c, Iterable)) # 结果为True

所以,只要一个对象中有__iter__方法,那么它就是可迭代对象。但此时,依旧不能用for循环来遍历它。

for 循环遍历的条件
  • 这个对象是可迭代对象(拥有__iter__方法)

  • 它的__iter__方法返回一个对象的引用。这个对象必须满足:

    • 这个对象是可迭代对象(拥有__iter__方法)
    • 这个对象拥有__next__方法

代码示例:

  1. """如何使它能够被for循环迭代??"""
  2. # 要求1:这个对象必须有__iter__方法
  3. # 要求2:它的__iter__方法必须返回 一个对象的引用。
  4. # 这个对象的要求是:1、有__iter__方法;2、有__next__方法。
  5. class Classmate:
  6. def __iter__(self):
  7. return ClassIterator() # 返回一个对象的引用
  8. # ↑↑ 这个对象ClassIterator() 它的内部包含__iter__和__next__方法
  9. class ClassIterator:
  10. def __iter__(self):
  11. pass
  12. def __next__(self):
  13. pass
  14. c = Classmate()

此时,可迭代对象c ,就可以被for循环遍历(理论上)。

对象c的__iter__方法的返回值:ClassIterator(),就是一个迭代器

当c被for循环时,就相当于调用ClassIterator()这个对象的__next__方法。__next__返回什么,for循环就得到什么。

简化迭代器

上例中,ClassIterator()对象只需要满足两个条件就可以作为迭代器,那么可以直接在原来的可迭代对象中新增__next__方法,让其既是可迭代对象,又是迭代器:

  1. import time
  2. from collections import Iterable, Iterator
  3. class Classmate:
  4. def __init__(self):
  5. self.names = list()
  6. self.current_num = 0
  7. def __iter__(self):
  8. return self
  9. def __next__(self):
  10. if self.current_num < len(self.names):
  11. ret = self.names[self.current_num]
  12. self.current_num += 1
  13. return ret
  14. else:
  15. raise StopIteration
  16. classmate = Classmate()
  17. classmate.add('小明')
  18. classmate.add('小花')
  19. classmate.add('小龙')
  20. for name in classmate:
  21. print(name)
  22. 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) # at 0x00000271725FF648>

  1. -
  2. 函数中有`yield`
  3. ```python
  4. def fib(count):
  5. a, b = 0, 1
  6. current_count = 0
  7. while current_count < count:
  8. # print(a)
  9. yield a # 把要使用时的print换成yield,这个函数被调用时,就获得了一个生成器
  10. a, b = b, a + b
  11. current_count += 1
  12. obj = fib(10)
  13. for i in obj:
  14. print(i)
  15. print('------------------------------------------')
  16. # 如果紧接着用for循环,会发现什么都打印不出来
  17. # for j in obj:
  18. # print(j)
  19. # 如果用next调用,会发现直接报错。(因为生成器中的值都已经被第一个for循环打印出来了)
  20. print(next(obj))
  21. # 如果 注释掉上面的for循环,这里就可以打印。且每次打印的值都是下一个的值。
  22. print(next(obj))
  23. print(next(obj))
  24. 如果向多次使用这个生成器,则 再创建一个生成器对象即可。
  25. obj2 = fib(20)
  26. print(next(obj2))


分析:for循环内,函数执行到yield a时 会暂停,把a直接打印出来,当for循环再次遍历obj时,函数会接着上次yield a的地方继续执行。

使用生成器完成多任务——协程

  1. import time
  2. def task_1():
  3. while True:
  4. print("---任务1执行中---")
  5. time.sleep(0.2)
  6. yield # 加上yield,让这个任务函数变成生成器
  7. def task_2():
  8. while True:
  9. print("---任务2执行中---")
  10. time.sleep(0.2)
  11. yield
  12. def main():
  13. t1 = task_1()
  14. t2 = task_2()
  15. while True:
  16. next(t1) # 利用生成器的方式切换任务,这就是 协程
  17. next(t2)
  18. if __name__ == '__main__':
  19. main()

协程方式使用的模块——greenlet、gevent

简单总结

  • 进程是资源分配的单位
  • 线程是操作系统调度的单位
  • 进程切换需要的资源很大,效率很低
  • 线程切换需要的资源一般,效率一般。(不考虑GIL)
  • 协程切换任务资源很小,效率高
  • 多进程、多线程根据CPU核数不一样,可能是并行的,但协程是在一个线程中,所以是并发