Python中的线程

线程同步

概念

线程同步,线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作.

临界区(Critical Section) 互斥量(Mutex) 信号量(Semaphore) 时间(Event)

Event事件

Event事件,是线程间通信机制中最简单的实现,使用一个内部标记的flag,通过flag的True或False的变化来进行操作.

名称 含义
set() 标记设置为True
clear() 标记设置为False
is_set() 标记是否为True
wait(timeout=None) 设置等标记为True的时长,None为无限等待.等到返回True,未等到超时返回False,不设置默认无限等

总结

使用同一个Event对象的标记flag

谁wait就是等到flag变为true,或等到超时返回False,不限制等待个数

wait的使用

Event的wait优于time.sleep,它会更快的切换到其他进程,提高并发效率.

Lock(互斥锁,互斥量)

锁,凡是存在共享资源争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源.

一旦线程获得锁,其他试图获取锁的线程将被阻塞

名称 含义
acquire(blocking=True,timeout=-1) 默认阻塞,阻塞可以设置超时时间.非阻塞时,timeout禁止设置.成功获取锁,返回True,否则返回False
release() 释放锁,可以从任何线程调用释放.已上锁的锁,会被重置为unlocked未上锁的锁上调用,抛RuntimeError异常
加锁,解锁

一般来说,有加锁就需要解锁,一旦加锁后解锁前,还要执行一些代码,就可能抛异常,锁无法释放,但是当前线程就可能因为这个异常被终止,产生死锁.一般为逻辑问题.

常用语句:
  1. 使用try,,,finally语句保证锁的释放
  2. with上下文管理,锁对象支持上下文管理
注意事项:
  • 少用.使用了锁,多线程访问被锁的资源时,就成了串行,要么排队执行,要么争抢执行
  • 加锁时间越短越好,不需要就立即释放锁
  • 一定要避免死锁

递归锁RLock(可重入锁)

递归锁,是线程相关锁,个人认为,锁的是一个线程

线程A可获得重复锁,并可多次成功获取,不会阻塞.最后要在线程A中做和acquire次数相同的release.

当锁未释放完,其他线程获取锁会被阻塞,知道当前持有锁的线程释放完锁

Condition

构造方法:Condition(lock= None),可以掺入一个Lock或者RLock锁对象,默认RLock

名称 含义
acquire(*args) 获取锁
wait() 等待或超时
notify(n= 1) 唤醒之多指定数目个数的等待线程,没有等待的线程就没有任何操作
notify_all() 唤醒所有等待的线程
总结

Condition用于生成者消费者模型中,解决生产者消费者速度匹配的问题.

采用通知机制,效率极高

使用方法:

使用Condition,必须先acquire,用完了要release,默认使用RLock.最好的方式是使用with上下文

消费者wait,等待通知

生产者生产好消息,对消费者发送通知,可以使用notify或者notify_all方法.

Barrier栅栏(路闸)

wait超时

semaphore信号量

和Lock很想,信号量对象内部维护一个倒计数器,每一次acquire都会减1,当acquire方法发现技术为0就阻塞请求的线程,知道其他线程对信号量release后,计数大于0,恢复阻塞的线程.

名称 含义
Semaphore(value=1) 构造方法.value小于0,抛ValueError异常
acquire() 获取信号量,计数器减1,成功返回True
release() 释放信号量,计数器加1

计数器永远不会低于0,因为acquire的时候,发现是0,都会被阻塞.

from threading import Semaphore,Lock
from threading import current_thread
from threading import Thread
import time

sm = Semaphore(5)#5个马桶
def task():
    sm.acquire()
    print(f'{current_thread().name} work')
    time.sleep(1)
    sm.release()
    
for line in range(20):
    t.Thread(target=task)
    t.start
信号量和锁

锁,值允许同一个时间一个线程独占资源,它是特殊的信号量,即信号量计数器初值为1.

信号量,可以多个线程访问共享资源,但这个共享资源数量有限.

数据结构和GIL

Queue

标准库queue模块,提供

FIFO的队列:先进先出

LIFO的队列:后进先出

优先队列:根据参数内,数字的大小进行分级,数值越小,优先级越高(字符串会有自己的算法,不建议使用)

Queue是线程安全的,适用于多线程间安全的交换数据.内部使用了Lock,Condition

import queue
q = queue.Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) #1
q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 3

GIL全局解释器锁(Global Interpreter Lock)

CPython在解释器进程级别有把锁.叫GIL全局解释器锁.

GIL保证CPython进程中,只有一个线程执行字节码,甚至多核CPU情况下,也会当一个进程A中的线程执行时,阻塞其他CPU上A进程的线程.相当于,CPython中永远没有真正微观实际意义上的多线程.

CPython中,IO密集型,使用多线程;CPU密集型,使用多继承,绕开GIL

Ruby也有GIL,如果高并发的话,用erlang或者go

​ Python中绝大多数内置数据结构读写都是原子操作.(即不会被优先级更高的线程打断,线程安全)

​ 由于GIL存在,Python的内置数据类型在多线程编程的时候就变程了安全的了,但是实际上他们本身不是线程安全类型.

原子操作:原子操作,就是不能被更高等级中断抢夺优先的操作。由于操作系统大部分时间处于开中断状态,所以,一个程序在执行的时候可能被优先级更高的线程中断。而有些操作是不能被中断的,不然会出现无法还原的后果,这时候,这些操作就需要原子操作。就是不能被中断的操作。

posted @ 2019-10-22 19:20  Agsol  阅读(179)  评论(0编辑  收藏  举报