对线程, 进程, 协程的理解
多任务:
操作系统可以同时运行多个任务
并行: 多个任务一起执行
并发: CPU快速切换执行
一. 线程(共享数据资源)
什么是线程? 调度执行的最小单位 线程是并发
1.threading模块
import time
import threading
def sing():
"""唱歌 5秒钟"""
for i in range(50):
print("----正在唱:菊花茶----")
time.sleep(0.1)
def dance():
"""跳舞 5秒钟"""
for i in range(50):
print("----正在跳舞----")
time.sleep(0.1)
def main():
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
t1.start() # 当调用start时候, 程序才执行, 开始调用
t2.start()
if __name__ == "__main__":
main()
2. 什么是多线程竞争(互斥锁) 银行家算法
在多线程中, 多个线程之间共享全局变量, 这个时候就出现了资源竞争的问题, 解决的办法就是加锁
# 同时也是线程安全
锁的好处: # 确保了同一时间内该线程数据的稳定传输性, 同一时间内执行单个线程
坏处: # 容易造成死锁
2.1 GIL 锁 全局解释器锁(只在 cpython 里才有)
作用:限制多线程同时执行,保证同一时间只有一个线程执行,所以 cpython 里的多线程其实是伪多线程!
解决办法: 可以使用java解释器, 或者调用C语言的代码. 在python语言中, 不会经常使用线程, 大多数使用
协程和进程.
二. 进程(不共享全局变量, 通过queue进行数据间的共享)
什么是进程? 程序运行在操作系统上的一个实例, 我们称之为进程
每一个进程都是独立存在的
1. multiprocessing 模块
import time
import multiprocessing
def 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() #当调用start时候, 程序才执行, 开始调用
p2.start()
if __name__ == "__main__":
main()
2.进程间的通信 queue 队列进行通信
from multiprocessing import Process, Queue
import os, time, random
# 写数据进程执行的代码:
def write(q):
for value in ['A', 'B', 'C']:
print('Put %s to queue...' % value)
q.put(value)
time.sleep(random.random())
# 读数据进程执行的代码:
def read(q):
while True:
if not q.empty():
value = q.get(True)
print('Get %s from queue.' % value)
time.sleep(random.random())
else:
break
if __name__=='__main__':
# 父进程创建Queue,并传给各个子进程:
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
# 启动子进程pw,写入:
pw.start()
# 等待pw结束:
pw.join()
# 启动子进程pr,读取:
pr.start()
pr.join()
# pr进程里是死循环,无法等待其结束,只能强行终止:
print('')
print('所有数据都写入并且读完')
3. 进程池的创建与使用
3.1为什么要使用进程池?
实际开发中, 当我们遇到子进程进程较多的时候, 可以使用进程池加快速度
from multiprocessing import Pool
import os, time, random
def worker(msg):
t_start = time.time()
print("%s开始执行,进程号为%d" % (msg,os.getpid()))
# random.random()随机生成0~1之间的浮点数
time.sleep(random.random()*2)
t_stop = time.time()
print(msg,"执行完毕,耗时%0.2f" % (t_stop-t_start))
po = Pool(3) # 定义一个进程池,最大进程数3
for i in range(0,10):
# Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
# 每次循环将会用空闲出来的子进程去调用目标
po.apply_async(worker,(i,))
print("----start----")
po.close() # 关闭进程池,关闭后po不再接收新的请求
po.join() # 等待po中所有子进程执行完成,必须放在close语句之后
print("-----end-----")
案列: 文件夹copy
import multiprocessing
import os
import time
import random
def copy_file(queue, file_name,source_folder_name, dest_folder_name):
"""copy文件到指定的路径"""
f_read = open(source_folder_name + "/" + file_name, "rb")
f_write = open(dest_folder_name + "/" + file_name, "wb")
while True:
time.sleep(random.random())
content = f_read.read(1024)
if content:
f_write.write(content)
else:
break
f_read.close()
f_write.close()
# 发送已经拷贝完毕的文件名字
queue.put(file_name)
def main():
# 获取要复制的文件夹
source_folder_name = input("请输入要复制文件夹名字:")
# 整理目标文件夹
dest_folder_name = source_folder_name + "[副本]"
# 创建目标文件夹
try:
os.mkdir(dest_folder_name)
except:
pass # 如果文件夹已经存在,那么创建会失败
# 获取这个文件夹中所有的普通文件名
file_names = os.listdir(source_folder_name)
# 创建Queue
queue = multiprocessing.Manager().Queue()
# 创建进程池
pool = multiprocessing.Pool(3)
for file_name in file_names:
# 向进程池中添加任务
pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))
# 主进程显示进度
pool.close()
all_file_num = len(file_names)
while True:
file_name = queue.get()
if file_name in file_names:
file_names.remove(file_name)
copy_rate = (all_file_num-len(file_names))*100/all_file_num
print("\r%.2f...(%s)" % (copy_rate, file_name) + " "*50, end="")
if copy_rate >= 100:
break
print()
if __name__ == "__main__":
main()
三. 协程, 迭代器, 生成器
1. 迭代器
1.1 可迭代对象?
定义: 如果成一个对象为可迭代对象, 即可以使用for循环, 那么必须实现__iter__方法
1.2 什么是迭代器?
定义: 一个对象具有__iter__方法和 __next__ 方法, 我们称之为迭代器
1.3 如何判断一个对象是可迭代对象?
# 进入ipython3 交互模式
from collections import Iterable
isinstance('abc',Iterable)
True
1.4 迭代器的应用
斐波拉契数列的实现
class Fibonacci(object):
def __init__(self, all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1
def __iter__(self):
return self
def __next__(self):
if self.current_num < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a+self.b
self.current_num += 1
return ret
else:
raise StopIteration
fibo = Fibonacci(10)
for num in fibo:
print(num)
数据类型之间的转换
2. 生成器
2.1 生成器的定义?
定义: 是一种特殊的迭代器, 具有yield关键字, 当一个函数中具有yield关键字的时候, 那么这个函数就是一个生成器.
2.2 yield关键字的作用
作用: 阻塞函数的进行, 将yield后面表达式的值进行返回. send进行唤醒
2.3 生成器的两种创建方式
1. yield关键字
2. 列表推导式, 将列表改为元组 (x for x in range(8))
3. 协程
3.1 协程的定义
定义: 用户态的轻量级线程
3.2 简单实现协程的demo
import time
def work1():
while True:
print("----work1---")
yield # 当一个函数中有yield关键字的时候, 那么不再是函数而是一个生成器
time.sleep(0.5)
def work2():
while True:
print("----work2---")
yield
time.sleep(0.5)
def main():
w1 = work1()
w2 = work2()
while True:
next(w1)
next(w2)
if __name__ == "__main__":
main()
3.3 使用greenlet来实现协程多任务
# 在python中, 有greenlet模块进行实现多任务
使用一下进行安装: sudo pip3 install 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中运行
gr1.switch()
# 输出:
---A--
---B--
---A--
---B--
3.4使用gevent 实现多任务
在上面的案列中我们发现, 启动多任务的时候, 总要自己手动切换, 那么gevent这个就可一自动切换
安装: pip3 install gevent
import gevent
def f(n):
for i in range(n):
print(gevent.getcurrent(), i)
#用来模拟一个耗时操作,注意不是time模块中的sleep
gevent.sleep(1)
g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()
注意: 有时候会有补丁, 这个时候需要我们自己去打补丁.
3.5 实现多个任务下载
from gevent import monkey
import gevent
import urllib.request
#有IO才做时需要这一句
monkey.patch_all()
def my_downLoad(file_name, url):
print('GET: %s' % url)
resp = urllib.request.urlopen(url)
data = resp.read()
with open(file_name, "wb") as f:
f.write(data)
print('%d bytes received from %s.' % (len(data), url))
gevent.joinall([
gevent.spawn(my_downLoad, "1.mp4", 'http://oo52bgdsl.bkt.clouddn.com/05day-08-%E3%80%90%E7%90%86%E8%A7%A3%E3%80%91%E5%87%BD%E6%95%B0%E4%BD%BF%E7%94%A8%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89.mp4'),
gevent.spawn(my_downLoad, "2.mp4", 'http://oo52bgdsl.bkt.clouddn.com/05day-03-%E3%80%90%E6%8E%8C%E6%8F%A1%E3%80%91%E6%97%A0%E5%8F%82%E6%95%B0%E6%97%A0%E8%BF%94%E5%9B%9E%E5%80%BC%E5%87%BD%E6%95%B0%E7%9A%84%E5%AE%9A%E4%B9%89%E3%80%81%E8%B0%83%E7%94%A8%28%E4%B8%8B%29.mp4'),
])
# 上面的url 可以换成自己的视频地址等等
1. 说说下面几个概念:同步,异步,阻塞,非阻塞?
1.1 同步, 异步 是相对于多任务来说
同步: 多个任务之间先后执行, 一个任务执行结束后, 进行下一个任务
异步: 多个任务之间相互执行, 一个任务未进行正常执行结束, 有可能是上一个任务执行的结果
1.2 阻塞, 非阻塞 相对于代码
阻塞: 当程序运行时, 无法继续向下执行时, 就是阻塞
非阻塞: 如果可以继续向下执行代码, 就是非阻塞
四. 僵尸进程和孤儿进程
1. 僵尸进程
1.1 什么是僵尸进程?
定义: 子进程执行完后, 而父进程无法将其子进程结束, 称之为僵尸进程
2. 孤儿进程
2.1 什么是孤儿进程?
定义: 一个父进程退出, 多个或者一个子进程还在执行就是孤儿进程, 被init进程所收集.
2.2 什么是init进程?
内核启动的第一个用户级进程
3. 如何避免僵尸进程?
使用kill -9 杀死, 或者重启计算机
五. 进程, 线程, 协程总结及之间的区别
1. 进程: 进程是操作系统正在进行的程序. 首先, 进程之间不共享全局变量, 它是通过queue队列进行数据间的共享. 其次, 每一个进程都是一个独立存在的.
进程消耗资源是最大的, 但是在处理 CPU 密集型有很大的优势, 另外 进程是并行的.
2. 线程: 不能独立存在, 必须依靠于进程. 首先, 线程间是共享全局变量的, 但是会出现多线程之间出现资源竞争的问题, 可以通过 锁 来进行解决. 但是要
避免出现死锁. 同时并没有真正的多线程, 由于GIL全局解释器的原因所导致. 解决这个原因可以使用java解释器. GIL锁是保证了同一时间内, 只允许运行一个
线程. 在处理 IO 密集型有优势. 另外, 线程是 并发.
3. 协程: 协程就是轻量级的线程, 由用户来进行控制, 不能进行共享资源. 主要用于 IO 密集型, 另外, 线程是并发的.
有需要的话可以关注我的微信公众号,会第一时间接收最新的知识。