一. 线程
1. 线程的基本概念
- 线程概念:操作系统调度的最小单位
- 主线程:程序启动时执行的主线,也称主线程
> 作用:1)创建子线程 2)等待子线程执行完成,并清理操作
- 子线程:程序执行的一个分支,由主线程创建
- 子线程创建
- 导入模块:
> import threading
- 创建线程对象:
> t1 = threading.Thread(target=函数名)
- 启动子线程:
> t1.start()
- 注意:多线程执行是无序的
-------------------------------------------------------------------------------------
线程-线程名称、总数量
- 获取线程的数量:正在活跃的线程数量
> 当前活跃的线程对象列表:
>
> threading.enumerate()
>
> 长度:
>
> 方法1: len(threading.enumerate())
>
> 方法2: threading.active_count()
- 获取线程的名称
> 获取当前的线程对象:threading.current_thread()
>
> 获取线程的名称: threading.current_thread().name
2, 线程的传参方式
# Thread 参数:
# target: 指定任务的函数名
# args : 元组传参数(参数列表...), 元组中的参数顺序就是函数的参数顺序
# kwargs : 字典传参数(key:value, key1:value1, ...), 不需要按照函数参数顺序传递;
import time
import threading
def worker(num,a,b):
print("a: {1}, b: {0}".format(a,b))
for i in range(num):
print("work---{}".format(threading.current_thread().name))
if __name__ == '__main__':
# t1 = threading.Thread(target=worker, args=(4,)) # 元组传参
# t1 = threading.Thread(target=worker, kwargs={"num": 2, "a": 6, "b": 7}) # 字典传参
t1 = threading.Thread(target=worker, args=(3,), kwargs={"a": 5, "b": 6})
t1.start()
"""
# 格式化输出的两种方式
a = 2
b = 3
print("输出a:%d,b: %d的值" %(a,b))
print("输出a:{},输出b:{}".format(a,b))
"""
3. 守护线程
线程守护:主线程和子线程的一种约定。主线程结束,所有的守护线程都结束。
使用:
方法1: 线程对象.setDaemon(True)
方法2: 线程对象 = threading.Thread(target=work1, args=(3,), daemon=True)
注意: 默认主线程负责: 1.创建子线程, 2.等待子线程结束, 回收资源
4. 并行和并发
-
并发: 任务数大于cpu核数(快速轮换执行)
-
并行: 任务数小于等于cpu核数(真正的同时运行)
5. 自定义线程类
自定义线程类的步骤
自定义线程类必须继承于threading.Thread
重写run方法
实例化自定义线程类的对象.start()启动自定义的线程
底层原理
Thread类
run方法
start()
start()中调用了run方法
自定义线程类的init方法问题
初始化自定义类时,需要先调用父类的__init__方法。
super(MyThread, self).__init__()
6. 线程的全局变量
1)多线程可以共享全局变量
2)多线程共享全局变量时可能出现资源竞争问题
-
解决方法:等待第一个线程执行结束,再执行下一个线程。
方法1: time.sleep(1)
方法2: 线程对象.join() (join阻塞等待线程执行结束再继续执行)
w1.join() : 等待 w1线程执行结束,再继续执行后面;
缺点:多线程实际上变成了单线程
3)同步
-
同步:一个任务执行完成,再执行下一个任务。
-
异步:多个任务同时执行,没有先后顺序。
-
线程的锁机制:多线程可以通过线程锁来保护公共资源。
7. 互斥锁
1)使用互斥锁
三步: 1.创建锁 lock = threading.Lock()
2.加锁 lock.acquire()
3.释放锁 lock.release()
2)死锁
-
死锁:当前线程有加锁,但是没有解锁。其他线程等待解锁,永远等不到。死锁
-
避免:一个加锁,一个解锁。return 之前一定要解锁。
# -*-coding:utf-8-*-
import time
import threading
"""
1. 两个子线程方法对一个变量同时写入,输出最终结果
"""
num = 0
lock = threading.Lock()
def worker1():
global num
for i in range(1000000):
# 加锁
lock.acquire()
num += 1
#释放锁
lock.release()
print("worker1中 num的值:{}".format(num))
def worker2():
global num
for i in range(1000000):
# 加锁
lock.acquire()
num += 1
lock.release()
print("worker2中 num的值:{}".format(num))
if __name__ == '__main__':
t1 = threading.Thread(target=worker1)
t2 = threading.Thread(target=worker2)
t1.start()
# time.sleep(1)
# t1.join()
t2.start()
# time.sleep(1)
# t2.join()
"""
一:
多线程共享全局变量时可能出现资源竞争问题
解决:1)time.sleep
2) 线程名.join()
二:
使用互斥锁
三步: 1.创建锁 lock = threading.Lock()
2.加锁 lock.acquire()
3.释放锁 lock.release()
"""
print("最终的总和为:{}".format(num))
二. 进程
1. 进程的基本概念
1. 进程:操作系统资源分配的最小单位,线程的容器
程序:可执行的文件
进程:运行起来的程序(程序代码+使用的系统资源)
一个进程最少有一个线程,就是主线程。
2. 进程的状态:
- 新建
- 就绪
- 运行
- 阻塞(等待)
- 死亡
就绪态:运⾏的条件都已经满⾜,正在等在cpu执⾏
执⾏态:cpu正在执⾏其功能
等待态:等待某些条件满⾜,例如⼀个程序sleep了,此时就处于等待态
2. 进程的基本使用
##进程使用的步骤:
1.导入模块: import multiprocessing
2.创建子进程对象: p1 = multiprocessing.Process(target=函数名)
3.启动子进程: p1.start()
## 其他函数:
join :等进程执行结束,再继续执行。
is_alive: 判断进程还活着,活着返回True。
terminate:不管进程是否执行完成,结束该进程。
// 主进程会等待子进程结束
# -*-coding:utf-8-*-
import multiprocessing
def sing():
for i in range(5):
print("{}唱歌中。。。".format('子进程'))
if __name__ == '__main__':
# 创建子进程
p1 = multiprocessing.Process(target=sing)
#运行
p1.start()
# join 等进程执行结束,再继续执行
# p1.join()
# is_alive 判断子进程是否活动,活动返回 True
print(p1.is_alive())
# terminate:不管进程是否执行完成,结束该进程。
p1.terminate()
for i in range(5):
print("{}唱歌中。。。".format('主进程'))
print('主进程结束')
3. 获取进程的 name / pid 信息
-
获取进程的名称
multiprocessing.current_process().name 设置名称:p1 = multiprocessing.Process(target=work1, name="MyProcess")
-
获取进程的编号
-
multiprocessing.current_process().pid
-
import os os.getpid()
-
-
获取进程的父id
import os os.getppid()
-
结束进程
p1.terminate() kill -9 pid
4. 进程参数传递
-
进程的参数传递
-
args 元组传递参数
p1 = multiprocessing.Process(target=work1, args=(1, 2, 3))
-
kwargs 字典传递参数
p1 = multiprocessing.Process(target=work1, kwargs={"a":1, "b":2, "c":3})
-
args 和 kwargs 组合传递参数
-
p1 = multiprocessing.Process(target=work1, args=(1,), kwargs={"b":2, "c":3})
-
-
5. 进程间共享全局变量的问题
- 多进程之间不能共享全局变量
- 主进程创建子进程的时候,实际上是对主进程完全拷贝。
- 主进程,从头开始执行;子进程,从进程处理函数那里开始,必须等主进程那边start 后才能启动。
6. 进程- 守护主进程
- p1.daemon=True 设置⼦进程 p1 守护主进程,当主进程结束的时候,⼦进程也随之结束
- p1.terminate() 终⽌进程执⾏,并⾮是守护进程
7. 进程,线程的对比
一。进程,线程的对比
-
进程消耗资源大于线程。
-
进程创建,切换,销毁更慢更复杂,线程反之。
-
进程不能共享全局变量;线程可以共享全局变量,需要使用互斥锁,使用互斥锁时不要死锁。
-
进程有自己独立的内存空间,线程共享进程的内存空间。
-
进程更可靠(一个进程死了,不会影响其他进程),进程中主要的线程死了,会影响整个进程。
-
进程支持多机多核扩容,线程只支持单机多核扩容
二。 不是非此即彼,而是组合使用 多进程+多线程
8. 消息队列的基本使用
- 消息队列的学习的目的:实现进程通信 (其他方式也能实现进程间通信:socket, 管道, 文件, ... )
- 队列的创建:
- Import multiprocessing
- q1 = multiprocessing.Queue(队列最大个数)
- 队列的操作
- 放入值(从队列尾部放入值): q1.put(obj) #(obj可以是任意类型) #如果队列满,阻塞等待队列有空间。
- 取值(从队列头部取值): q1.get() #如果队列空,阻塞等待队列中有新的数据来。
- 消息队列 非阻塞的方式:
- 放入值: p1.put(obj, block=False) or p1.put_nowait(obj)
- 取值 : p1.get(block=False) or p1.get_nowait()
- 消息队列-- 常见判断
- 判断是否已满: q1.full()
- 判断是否为空: q1.empty()
- 取出队列中消息的数量 : q1.qsize()
9. Queue 消息队列实现进程间通信
- 导入模块
- 创建一个空队列
- 创建写进程,读进程
- 启动进程
-
# -*-coding:utf-8-*- import multiprocessing def write_queue(queues): for i in range(10): if queues.full(): print("消息队列已满:", queues.qsize()) break queues.put(i) def read_queue(queues): while True: if queues.empty(): print("消息队列已空", queues.qsize()) break print(queues.get()) if __name__ == '__main__': # 创建消息队列 queues = multiprocessing.Queue(5) # 创建两个子进程 write_process = multiprocessing.Process(target =write_queue, args=(queues,)) read_process = multiprocessing.Process(target =read_queue, args=(queues,)) # 执行进程 write_process.start() write_process.join() read_process.start()
·10. 进程池 Pool
1) 进程池:管理进程的工具,它帮我们实现进程创建,销毁,调度进程。
2)进程池中创建的进程默认是守护进程,主进程结束,子进程立即结束.
3) 创建方法:
1)导入模块: import multiprocessing
2) 创建进程池:pool = multiprocessing.Pool(3) #3代表进程池中最多的进程个数
4)运行方式:
1)同步方式: 一个任务完成,再执行下一个任务
pool.apply(copy_work)
2)异步方式: 无序,同步执行
pool.apply_async(copy_work)
# 异步方式如果 join之前必须先close
1) 进程池要 close() 表示不再接受新的任务 pool.close()
2)还要join() 表示让主进程等待进程池执行结束后再退出 pool.join()
# -*-coding:utf-8-*-
import multiprocessing
import time
def copy_works():
print("工作中", multiprocessing.current_process().name)
if __name__ == '__main__':
# 创建进程池
pool = multiprocessing.Pool(5)
for i in range(10):
# 同步方式
pool.apply(copy_works)
# 异步方式
# pool.apply_async(copy_works)
# 如果是异步方式需要加上 join(进程池中创建的进程默认是守护进程)
# 使用 join 之前,需要先关闭进程池
# pool.close()
# pool.join()
print('主进程结束')
11. 进程池中的Queue(消息队列)
1)获取方法:
# 2.创建一个空队列(最大3个)
queue = multiprocessing.Manager().Queue(3)
# 5.创建一个进程池
pool = multiprocessing.Pool(2)
2)提交任务方法1:
# 6.提交写队列的任务
pool.apply(write_queue, args=(queue,))
# 7.提交读队列的任务
pool.apply(read_queue, args=(queue, ))
3)提交任务方法2:
# apply_async() : 异步提交方法返回一个applyResult结果集
result = pool.apply_async(write_queue, args=(queue, ))
# wait() : 等待第一个任务执行完成再执行下一个.
result.wait()
# # 7.提交读队列的任务
pool.apply_async(read_queue, args=(queue, ))
pool.close()
pool.join()
#详细代码
# -*-coding:utf-8-*-
# 1.导入模块
# 2.创建一个空队列
# 3.创建函数
# 4.创建进程池
# 5.提交任务
import multiprocessing
def writes(queue):
for i in range(10):
if queue.full():
print("消息队列已满", queue.qsize())
break
queue.put(i)
def reads(queue):
while True:
if queue.empty():
print('消息队列已空', queue.qsize())
break
print(queue.get())
if __name__ == '__main__':
queue = multiprocessing.Manager().Queue(3)
pool = multiprocessing.Pool(2)
# 进程池同步
# pool.apply(writes, args=(queue,))
# pool.apply(reads, args=(queue,))
# 进程池异步
result=pool.apply_async(writes, args=(queue,))
result.wait()
pool.apply_async(reads, args=(queue,))
pool.close()
pool.join()
print('主进程结束')
三. 迭代器
1. 可迭代对象及检测方法
1)可迭代对象: 可以被for循环遍历(迭代)的对象,就是可迭代对象。
# for循环可迭代的对象有: list tuple str dict set range
2)可迭代对象的本质:一个对象的类中包含了函数__iter__
,那么这个对象就是可迭代对象
3)可迭代对象的检测:
from collections import IterabZle
isinstance(对象,Iterable)
返回True:可迭代对象
返回False:不可迭代对象
2. 迭代器及使用方法
1)迭代器的作用: 1)记住当前遍历(迭代)的位置 2)返回下一个数据(可迭代对象) ;
2)获取迭代器 : iter(可迭代对象)
3) 获取可迭代对象(迭代器)的值 : next(迭代器)
4)判断是否为迭代器: isinstance(demo, Iterator)
# -*-coding:utf-8-*-
from collections import Iterator, Iterable
if __name__ == '__main__':
lists = [1, 2, 3]
# 获取迭代对象的迭代器
demo = iter(lists)
# 输出迭代器的值
print(next(demo))
print(next(demo))
# print(next(demo))
# 是否为可迭代对象
print(isinstance(lists, Iterable))
# 检测是否为可迭代对象
print(isinstance(demo, Iterator))
5) for循环的本质:
1)获取可迭代对象的迭代器
2)通过next不停的获取迭代器的数据
3)捕获StopIteration异常,结束遍历(迭代)
# for循环的本质:
# 1.
# 取出可迭代对象的迭代器
myiter = iter(data_list)
# 2.
# 不停next获取下一个数据
try:
while True:
result = next(myiter)
print(result)
# 3.
# 捕获StopIteration异常,结束遍历(迭代)
except StopIteration as s:
pass
3. 迭代器也是可迭代对象
- 可迭代对象: 这个对象的类实现了__iter__方法
- 迭代器:这个对象的类实现了__iter__和__next__方法
- 结论:迭代器也是可迭代对象
4. 生成器
1)生成器概念: 按照一定规律生成一系列数据,也是一个特殊的迭代器
2)生成器的创建: 把⼀个列表⽣成式的 [ ] 改成 ( )
l1 = (x*x for x in range(10))
print(l1)
3)yield 的作用:
# yield: 1. 创建生成器
# 2. next 启动生成器, 在yield停止执行,返回result
# 3. 第二次next 启动生成器, 再yield暂停的位置继续执行,直到下一次
# yield位置
# 4. 生成器函数结束,抛出异常StopIteration
4)return 的作用 :终止生成器的迭代,抛出异常StopIteration,异常的信息,就是return的返回值
5)send的作用,能唤醒生成器,也能传递参数
f1 = Fibonacci(5)
# send第一次启动生成器不能传参数(所以第一次启动生成器使用next)
result = f1.send(None)
print(result)
result = f1.send("数据2")
print(result)
四. 协程
1. 协程的定义:
1)用户级线程,微线程,纤程。
2)线程和进程最终都是系统调度,无法控制执行的顺序
3)协程是用户自己控制的多任务,可以控制执行的顺序
2. 手动实现了协程
# 1.定义work1和work2生成器
# 2.创建两个生成器的对象
# 3.循环切换生成器执行
import time
# 1.定义work1和work2生成器
def work1():
while True:
print("任务1......")
time.sleep(0.5)
yield
def work2():
while True:
print("任务2......")
time.sleep(0.5)
yield
if __name__ == '__main__':
# 2.创建两个生成器的对象
w1 = work1()
w2 = work2()
# 3.循环切换生成器执行
while True:
next(w1)
next(w2)
3. greenlet 实现协程
1)导入模块 : from greenlet import greenlet
2) 创建 greenlet 对象
g1 = greenlet(work1)
#g1 = greenlet(函数名)
3) 切换任务
# g1.switch(*args, **kwargs)启动协议
# args: 顺序传参
# kwargs: 关键字传参
g1.switch(2, b=12, c=34)
4. gevent 实现协程 (gevent 也是第三方库,自动调度协程,自动识别程序中的耗时操作 )
1)导入模块 import gevent
2) 指派任务 g1 = gevent.spawn(函数名, *args, **kwargs)
3)join() 让主线程等待协程执行完毕后再退出 g1.join()
5. gevent 默认不能识别耗时操作的问题
1) 替换time.sleep() ---> gevent.sleep()
2)打猴子补丁
#1、导入模块
from gevent import monkey
#2、破解所有:
monkey.patch_all()
# 猴子补丁的作用:使得gevent可以识别系统的耗时操作
# -*-coding:utf-8-*-
import gevent
# 使用猴子补丁
from gevent import monkey
monkey.patch_all()
import time
def work1():
for i in range(5):
print("woke1-----")
# gevent.sleep(0.5)
# 由于gevent 不能识别 time.sleep 的耗时操作,需要使用猴子补丁
time.sleep(0.5)
def work2():
for i in range(5):
print("work2-----------")
gevent.sleep(0.5)
if __name__ == '__main__':
# t1 就等待被自动切换,gevent判断代码遇到 io 耗时操作时,会自动切换协程
t1 = gevent.spawn(work1)
t2 = gevent.spawn(work2)
print(111)
t1.join()
# t2.join()
print(222)
五. 进程、线程、协程区别
1. 三者对比:
-
进程: 资源分配的基本单位、线程:系统调度的基本单位 、协程: 用户级线程
-
切换效率: 协程 高于 线程 高于 进程
-
高效率方式: 多进程+多协程/多线程
2. 选择问题
多进程:密集CPU任务,需要充分使用多核CPU资源(服务器,大量的并行计算)的时候,用多进程。
缺陷:多个进程之间通信成本高,切换开销大。
多线程:密集I/O任务(网络I/O,磁盘I/O,数据库I/O)使用多线程合适。
(i/o,是input/output的缩写,即输入输出端口,每个设备都会有一个专用的i/o地址,用来处理自己的输入输出信息)
缺陷:同一个时间切片只能运行一个线程,不能做到高并行,但是可以做到高并发。
协程:当程序中存在大量不需要CPU的操作时(IO),适用于协程;
缺陷:单线程执行,处理密集CPU和本地磁盘IO的时候,性能较低。处理网络I/O性能还是比较高.