对线程, 进程, 协程的理解

进入主题之前我们先了解什么是多任务


多任务:
操作系统可以同时运行多个任务
并行: 多个任务一起执行
并发: 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 密集型, 另外, 线程是并发的.

有需要的话可以关注我的微信公众号,会第一时间接收最新的知识。

 

 

 

 

 

 
posted on 2018-07-31 17:15  liudemeng  阅读(395)  评论(0编辑  收藏  举报