Python 部分知识点总结(十)

此篇博客只是记录第十二周未掌握或不熟悉的知识点,用来加深印象。

一、线程同步

  1. 线程同步:线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作
    不同操作系统实现技术有所不同,有临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)等
  2. Event 是使用一个内部的标记 flag,通过 flag 的 True 或 False 的变化来进行操作
            set():标记设置为 True
            clear():标记设置为 False
            is_set():标记是否为 True
            wait(timeout=None):设置等待标记为 True 的时长,None 为无限等待,等到返回 True,未等到超时了返回 False
  3. wait 的使用
    from threading import Event, Thread
    import logging
    logging.basicConfig(level=logging.INFO)
    def do(event, interval):
        while not event.wait(interval):
            logging.info(‘do sth’)
    e = Event()
    Thread(target=do, args=(e, 3)).start()
    e.wait(10)
    e.set()
    print(‘main exit’)
    Event 的 wait 优于 time.sleep,它会更快的切换到其他线程,提高并发效率
  4. 实现 Timer,延时执行的线程,延时计算 add(x, y)
    from threading import Event, Thread
    import logging
    import datetime
    logging.basicConfig(level=logging.INFO)
    def add(x, y):
        logging.info(x + y)
    class Timer:
        def __init__(self, interval, fn, *args, **kwargs):
            self.interval = interval
            self.fn = fn
            self.args = args
            self.kwargs = kwargs
            self.event = Event()
        def start(self):
            Thread(target=self.__run).start()
        def cancel(self):
            self.event.set()
        def __run(self):
            start = datetime.datetime.now()
            logging.info(‘waiting’)
            self.event.wait(self.interval)
            if not self.event.is_set():
                self.fn(*self.args, **self.kwargs)
            delta = (datetime.datetime.now() – start).total_seconds()
            logging.info(‘finished {}’.format(delta))
            self.event.set()
    t = Timer(10, add, 4, 50)
    t.start()
    e = Event()
    e.wait(4)
    print(‘=========’)
  5. 锁:凡是存在共享资源争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源,一旦线程获得锁,其它师徒获取锁的线程将被阻塞
    acquire(blocking=True, timeout=-1):默认阻塞,阻塞可以设置超时时间。非阻塞时,timeout 禁止设置。成功获取锁,返回 True,否则返回 False
    release():释放锁,可以从任何线程调用释放,已上锁的锁,会被重置为 unlocked,未上锁的锁被调用,会抛出 RuntimeError 异常
  6. 死锁:一般来说,加锁就需要解锁,但是加锁后解锁前,还要有一些代码执行,就有可能抛异常,一旦出现异常,锁是无法释放,但是当前线程可能因为这个异常被终止了,这就产生了死锁。
    加锁、解锁常用语句:
            使用 try … finally 语句保证锁的释放
            with 上下文管理,锁对象支持上下文管理
  7. 锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候
          如果全部都是读取同一个共享资源,那就不需要锁,因为这时可以认为共享资源是不可变的,每一次服务它都是一样的值
    使用锁的注意事项:
            少用锁,必要时用锁,使用了锁,多线程访问被锁的资源时,就成了串行,要么排队执行,要么争抢执行
            加锁时间越短越好,不需要就立即释放锁
            一定要避免死锁
  8. 可重入锁(RLock),是线程相关的锁,可在一个线程中获取锁,并可继续在同一线程中不阻塞获取锁。当锁未释放完,其它线程获取锁就会阻塞,直到当前持有锁的线程释放完锁。
  9. 构造方法 Condition(lock=None),可以传入一个 Lock 或 RLock 对象,默认是 RLock
    acquire:获取锁
    wait(self, timeout=None):等待或超时
    notify(n=1):唤醒至多指定数目个数的等待的线程,没有等待的线程就没有任何操作
    notify_all():唤醒所有等待的线程
    总结:Condition 用于生产者消费者模型中,解决生产者消费者速度匹配的问题,采用了通知机制,非常有效率
    使用方式:使用 Condition,必须先 acquire,用完了要 release,因为内部使用了锁,默认使用 RLock 锁,最好的方式是使用 with 上下文
                    消费者 wait,等待通知
                    生产者生产后对消费者发通知,可以使用 notify 或者 notify_all 方法

二、并发和线程

  1. 并行(parallel):同时做某些事,可以互不干扰的同一时刻做几件事(同时发生的概念)
    并发(concurrency):同时做某些事,但是强调,一个时段内有是事情要处理(众多车辆在这一段要通过路面的事件就是并发,车很多就是高并发)
  2. 并发的解决:队列和缓冲区(缓冲区、优先队列)、争抢(锁机制)、预处理、并行(水平扩展)、提速(垂直扩展)、消息中间件(站外的九曲回肠的走廊)
  3. 线程:是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运算单位
                    一个程序的执行实例就是一个进程
                    有时被称为轻量级进程,是程序执行流的最小单元
                    一个标准的线程有线程 ID、当前指令指针、寄存器集合和堆栈组成
                    在许多系统中,创建一个线程比创建一个进程快 10 – 100 倍
    进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统运行资源分配和调度的基本单位,是操作系统结构的基础
              现代操作系统提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源
              进程就是独立的王国,进程间不可以随便的共享数据
    关系:程序是源代码编译后的文件,而这些文件存放在磁盘上,当程序被操作系统加载到内核中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器
              线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程拥有自己独立的堆栈
  4. 线程的状态
            就绪(Ready):线程能够运行,但在等待被调度,可能线程刚刚创建启动,或刚刚从阻塞中恢复,或者被其他线程枪占
            运行(Running):线程正在运行
            阻塞(Blocked):线程等待外部事件发生而无法运行,如 I/O 操作
            终止(Terminated):线程完成,或退出,或被取消
  5. Thread 类签名:def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)
    target:线程调用的对象,就是目标函数
    name:为线程起个名字
    args:为目标函数传递参数,元祖
    kwargs:为目标函数关键字传参,字典
    线程之所以执行函数,是因为线程中就是执行代码的,而最简单的封装就是函数,所以还是函数调用
  6. 线程退出:线程函数内语句执行完毕的时候 或者 线程函数中抛出未处理的异常
  7. threading 的属性和方法
    current_thread():返回当前线程对象
    main_thread:返回主线程对象
    active_count():当前出来 alive 状态的线程个数,包括主线程
    enumerate():返回所有或者的线程的列表,不包括已经终止的线程和未开始的线程,包括主线程
    get_ident():返回当前线程带的 ID,非 0 整数
  8. Thread 实例的属性和方法
    name:只是一个名字,只是个标识,名称可以重名,getName(),setName() 获取、设置这个名词
    ident:线程 ID,它是非 0 整数,线程启动后才会有 ID,否则为 None,线程退出,此 ID 依旧可以访问,此 ID 可以重复使用
    is_alive:返回线程是否活着
    注意:线程的 name 是一个名称,可以重复;ID 必须唯一,但可以在线程退出后再利用
  9. start() 方法会调用 run() 方法,而 run() 方法可以运行函数
    使用 start 方法启动线程,启动了一个新的线程,名字叫做 worker 运行
    使用 run 方法并没有启动新的线程,就是在主线程中调用一个普通的函数而已
    因此,启动线程请使用 start 方法,才能启动多个线程
  10. 一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程,一个进程至少有一个主线程,其他线程称为工作线程
  11. print 函数是 线程不安全 的,两个解决办法:不让 print 打印换行;使用 logging
    import threading
    def worker():
        for x in range(100):
            print(“{} is runnning.\n”.format(threading.current_thread().name), end=”)
    for x in range(1, 5):
        name = “worker{}”.format(x)
        t =threading.Thread(name=name, target=worker)
        t.start()
    字符串是不可变的类型,它可以作为一个整体不可分割输出,end=” 就不再让 print 输出换行了
    import threading
    import logging
    def worker():
        for x in range(100):
            logging.warning(“{} is running.”.format(threading.current_thread().name))
    for x in range(1, 5):
        name = “worker{}”.format(x)
        t =threading.Thread(name=name, target=worker)
        t.start()
    标准库里面的 logging 模块,日志处理模块,线程安全的,生成环境代码都是用 logging
  12. Python 中,构造线程的时候,可以设置 daemon 属性,这个属性必须在 start 方法前设置好
                    线程 daemon 属性,如果设定就是用户的设置,否则就取消当前线程的 daemon 值
                    主线程是 non-daemon 线程,即 daemon = False
    应用场景:
            后台任务,如发送心跳包、监控,这种场景最多
            主线程工作才有用的线程,如主线程中维护着公共的资源,主线程已经清理了,准备退出,而工作线程使用这些资源工作也没有意义了,一起退出最合适
            随时可以被终止的线程
    daemon 属性:表示线程是否是 daemon 线程,这个值必须在 start() 之前设置,否则引发 RuntimeError 异常
    isDaemon():是否是 daemon 线程
    setDaemon:设置为 daemon 线程,必须在 start方法之前设置
    总结:
            线程具有一个 daemon 属性,可以显示设置为 True 或 False,也可以不设置,则取默认值 None
            如果不设置 daemon,就取当前线程的 daemon 来设置它
            主线程是 non-daemon 线程,即 daemon = False
            从主线程创建的所有线程都不设置 daemon 属性,则默认都是 daemon = False,也就是 non-daemon线程
            Python 程序在没有活着的 non-daemon 线程运行时退出,也就是剩下的只能是 daemon 线程,主线程才能退出,否则主线程就只能等待
  13. join(timeout=None),是线程的标准方法之一
            一个线程中调用另一个线程的 join 方法,调用者将被阻塞,直到被调用线程终止
            一个线程可以被 join 多次
            timeout 参数指定调用者等待多久,没有设置超时,就一直等到被调用线程结束
            调用谁的 join 方法,就是 join 谁,就要等谁
  14. Python 提供 threading.local 类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其他线程看不见
    import threading
    import time
    global_data = threading.local()
    def worker():
        global_data.x = 0
        for i in range(100):
            time.sleep(0.001)
            global_data.x += 1
        print(threading.current_thread(), global_data.x)
    for i in range(10):
        threading.Thread(target=worker).start()
    threading.local 类构建了一个大字典,其元素是每一线程实例的地址为 key 和线程对象引用线程单独的字典的映射,如:{ id(Thread) -> (ref(Thread), thread-local dict) },通过 threading.local 实例就可在不同的线程中,安全地使用线程独有的数据,做到了线程间数据隔离,如同本地变量一样安全
  15. threading.Timer 继承自 Thread,这个类用来定义多久执行一个函数
            class threading.Timer(interval, function, args=None, kwargs=None)
            start 方法执行之后,Timer 对象会处于等待状态,等待了 interval 之后,开始执行 function 函数
            如果在执行函数之前的等待阶段,使用了 cancel 方法,就会跳过执行函数结束
            可以用 setDaemon 来设置 daemon,如果设置成 True 就算 Timer 有 interval 也不管了
    总结:Timer 是线程 Thread 的子类,就是线程类,具有线程的能力和特征
              它的实例是能够延时执行目标函数的线程,在真正执行目标函数之前,都可以 cancle 它

本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/99642

发表评论

登录后才能评论

联系我们

400-080-6560

在线咨询:点击这里给我发消息

邮件:1823388528@qq.com

工作时间:周一至周五,9:30-18:30,节假日同时也值班