zoukankan      html  css  js  c++  java
  • 对线程, 进程, 协程的理解

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


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

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

     

     
  • 相关阅读:
    自考新教材-p282
    p281
    自考新教材-p279_2
    用jmap分析java程序
    用jstack工具分析java程序
    java应用maven插件动态生成webservice代码
    Java对信号的处理
    【OracleDB】 01 概述和基本操作
    【Oracle】Windows-19C 下载安装
    【Hibernate】06 查询API
  • 原文地址:https://www.cnblogs.com/liudemeng/p/9397028.html
Copyright © 2011-2022 走看看