高阶知识点1.2 (线程、进程、迭代器、协程)

一. 线程

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 信息

  1. 获取进程的名称

    multiprocessing.current_process().name
    
    设置名称:p1 = multiprocessing.Process(target=work1, name="MyProcess")
  • 获取进程的编号

    1.  multiprocessing.current_process().pid

    2. 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, 管道, 文件, ... )
  • 队列的创建:
  1.  Import multiprocessing
  2. q1 = multiprocessing.Queue(队列最大个数)
  • 队列的操作
  1. 放入值(从队列尾部放入值):  q1.put(obj) #(obj可以是任意类型)  #如果队列满,阻塞等待队列有空间。
  2. 取值(从队列头部取值):  q1.get()  #如果队列空,阻塞等待队列中有新的数据来。

 

  • 消息队列 非阻塞的方式:
  1. 放入值: p1.put(obj, block=False)     or     p1.put_nowait(obj)
  2. 取值 :   p1.get(block=False)            or    p1.get_nowait()

 

  • 消息队列-- 常见判断
  1. 判断是否已满:       q1.full()
  2. 判断是否为空:        q1.empty()
  3. 取出队列中消息的数量 :    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性能还是比较高.

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值