一、线程同步
-
线程同步:线程间协同,通过某种技术,让一个线程访问某些数据时,其他线程不能访问这些数据,直到该线程完成对数据的操作不同操作系统实现技术有所不同,有临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)等
-
Event 是使用一个内部的标记 flag,通过 flag 的 True 或 False 的变化来进行操作set():标记设置为 Trueclear():标记设置为 Falseis_set():标记是否为 Truewait(timeout=None):设置等待标记为 True 的时长,None 为无限等待,等到返回 True,未等到超时了返回 False
-
wait 的使用from threading import Event, Threadimport logginglogging.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,它会更快的切换到其他线程,提高并发效率
-
实现 Timer,延时执行的线程,延时计算 add(x, y)from threading import Event, Threadimport loggingimport datetimelogging.basicConfig(level=logging.INFO)def add(x, y):logging.info(x + y)class Timer:def __init__(self, interval, fn, *args, **kwargs):self.interval = intervalself.fn = fnself.args = argsself.kwargs = kwargsself.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(‘=========’)
-
锁:凡是存在共享资源争抢的地方都可以使用锁,从而保证只有一个使用者可以完全使用这个资源,一旦线程获得锁,其它师徒获取锁的线程将被阻塞acquire(blocking=True, timeout=-1):默认阻塞,阻塞可以设置超时时间。非阻塞时,timeout 禁止设置。成功获取锁,返回 True,否则返回 Falserelease():释放锁,可以从任何线程调用释放,已上锁的锁,会被重置为 unlocked,未上锁的锁被调用,会抛出 RuntimeError 异常
-
死锁:一般来说,加锁就需要解锁,但是加锁后解锁前,还要有一些代码执行,就有可能抛异常,一旦出现异常,锁是无法释放,但是当前线程可能因为这个异常被终止了,这就产生了死锁。加锁、解锁常用语句:使用 try … finally 语句保证锁的释放with 上下文管理,锁对象支持上下文管理
-
锁适用于访问和修改同一个共享资源的时候,即读写同一个资源的时候如果全部都是读取同一个共享资源,那就不需要锁,因为这时可以认为共享资源是不可变的,每一次服务它都是一样的值使用锁的注意事项:少用锁,必要时用锁,使用了锁,多线程访问被锁的资源时,就成了串行,要么排队执行,要么争抢执行加锁时间越短越好,不需要就立即释放锁一定要避免死锁
-
可重入锁(RLock),是线程相关的锁,可在一个线程中获取锁,并可继续在同一线程中不阻塞获取锁。当锁未释放完,其它线程获取锁就会阻塞,直到当前持有锁的线程释放完锁。
-
构造方法 Condition(lock=None),可以传入一个 Lock 或 RLock 对象,默认是 RLockacquire:获取锁wait(self, timeout=None):等待或超时notify(n=1):唤醒至多指定数目个数的等待的线程,没有等待的线程就没有任何操作notify_all():唤醒所有等待的线程总结:Condition 用于生产者消费者模型中,解决生产者消费者速度匹配的问题,采用了通知机制,非常有效率使用方式:使用 Condition,必须先 acquire,用完了要 release,因为内部使用了锁,默认使用 RLock 锁,最好的方式是使用 with 上下文消费者 wait,等待通知生产者生产后对消费者发通知,可以使用 notify 或者 notify_all 方法
二、并发和线程
-
并行(parallel):同时做某些事,可以互不干扰的同一时刻做几件事(同时发生的概念)并发(concurrency):同时做某些事,但是强调,一个时段内有是事情要处理(众多车辆在这一段要通过路面的事件就是并发,车很多就是高并发)
-
并发的解决:队列和缓冲区(缓冲区、优先队列)、争抢(锁机制)、预处理、并行(水平扩展)、提速(垂直扩展)、消息中间件(站外的九曲回肠的走廊)
-
线程:是操作系统能够进行运算调度的最小单位,被包含在进程之中,是进程中的实际运算单位一个程序的执行实例就是一个进程有时被称为轻量级进程,是程序执行流的最小单元一个标准的线程有线程 ID、当前指令指针、寄存器集合和堆栈组成在许多系统中,创建一个线程比创建一个进程快 10 – 100 倍进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统运行资源分配和调度的基本单位,是操作系统结构的基础现代操作系统提出进程的概念,每一个进程都认为自己独占所有的计算机硬件资源进程就是独立的王国,进程间不可以随便的共享数据关系:程序是源代码编译后的文件,而这些文件存放在磁盘上,当程序被操作系统加载到内核中,就是进程,进程中存放着指令和数据(资源),它也是线程的容器线程就是省份,同一个进程内的线程可以共享进程的资源,每一个线程拥有自己独立的堆栈
-
线程的状态就绪(Ready):线程能够运行,但在等待被调度,可能线程刚刚创建启动,或刚刚从阻塞中恢复,或者被其他线程枪占运行(Running):线程正在运行阻塞(Blocked):线程等待外部事件发生而无法运行,如 I/O 操作终止(Terminated):线程完成,或退出,或被取消
-
Thread 类签名:def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)target:线程调用的对象,就是目标函数name:为线程起个名字args:为目标函数传递参数,元祖kwargs:为目标函数关键字传参,字典线程之所以执行函数,是因为线程中就是执行代码的,而最简单的封装就是函数,所以还是函数调用
-
线程退出:线程函数内语句执行完毕的时候 或者 线程函数中抛出未处理的异常
-
threading 的属性和方法current_thread():返回当前线程对象main_thread:返回主线程对象active_count():当前出来 alive 状态的线程个数,包括主线程enumerate():返回所有或者的线程的列表,不包括已经终止的线程和未开始的线程,包括主线程get_ident():返回当前线程带的 ID,非 0 整数
-
Thread 实例的属性和方法name:只是一个名字,只是个标识,名称可以重名,getName(),setName() 获取、设置这个名词ident:线程 ID,它是非 0 整数,线程启动后才会有 ID,否则为 None,线程退出,此 ID 依旧可以访问,此 ID 可以重复使用is_alive:返回线程是否活着注意:线程的 name 是一个名称,可以重复;ID 必须唯一,但可以在线程退出后再利用
-
start() 方法会调用 run() 方法,而 run() 方法可以运行函数使用 start 方法启动线程,启动了一个新的线程,名字叫做 worker 运行使用 run 方法并没有启动新的线程,就是在主线程中调用一个普通的函数而已因此,启动线程请使用 start 方法,才能启动多个线程
-
一个进程中至少有一个线程,并作为程序的入口,这个线程就是主线程,一个进程至少有一个主线程,其他线程称为工作线程
-
print 函数是 线程不安全 的,两个解决办法:不让 print 打印换行;使用 loggingimport threadingdef 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 threadingimport loggingdef 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
-
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 线程,主线程才能退出,否则主线程就只能等待
-
join(timeout=None),是线程的标准方法之一一个线程中调用另一个线程的 join 方法,调用者将被阻塞,直到被调用线程终止一个线程可以被 join 多次timeout 参数指定调用者等待多久,没有设置超时,就一直等到被调用线程结束调用谁的 join 方法,就是 join 谁,就要等谁
-
Python 提供 threading.local 类,将这个类实例化得到一个全局对象,但是不同的线程使用这个对象存储的数据其他线程看不见import threadingimport timeglobal_data = threading.local()def worker():global_data.x = 0for i in range(100):time.sleep(0.001)global_data.x += 1print(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 实例就可在不同的线程中,安全地使用线程独有的数据,做到了线程间数据隔离,如同本地变量一样安全
-
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