临界区:
临界区指的是一个访问共用资源(例如:共用设备或是共用存储器)的程序片断,而这些共用资源又没法同时被多个线程访问的特性。当有线程进入临界区段时,其余线程或是进程必须等待,有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥得到使用。python
咱们在多线程处理是,常常会涉及到对于一块共享资源的访问,这里就须要线程能够互斥去避免出现竞态条件(race condition).web
要想让可变对象安全地在多线程环境中进行访问,就要使用锁变量(threading中的Lock\RLock),锁变量也是同步原语中的最多见一种之一。安全
import threading class ShareCounter: ''' 一个能够在多线程中分享的类 ''' def __init__(self, initial_value=0): self._value = initial_value self._value_lock = threading.Lock() def add_one(self, delta=1): with self._value_lock:# with 上下文管理器,是的锁的申请和释放更加方便(自动释放),更好地保证了互斥 # 一样很好地避免了死锁 self._value += delta#执行缩进语句时得到锁,缩进语句结束后自动释放锁 def sub_one(self): with self._value_lock: self._value -= 1 #with管理器彻底等同于以下操做(老版本的python中): # self._value_lock.acquire() # self._value -= 1 # self._value_lock.release()
线程的调度从本质上来讲是非肯定性的(只能保证独一访问,但保证不了谁先谁后)。只要共享的可变状态须要被多个线程访问,就要使用锁机制,保证数据的安全。多线程
在threading库中咱们也发现了其余的同步原语例如:RLock(可重入锁)、Semaphore对象(信号量)。
RLock:能够被同一个线程屡次获取,主要用来编写基于锁的代码,或者基于‘监听器’的同步处理。
当某个类持有这种类型锁时,只有一个线程可使用类中所有函数或者方法,例如:并发
import threading class ShareCounter: ''' 一个能够在多线程中分享的类 ''' _lock = threading.RLock() def __init__(self, initial_value=0): self._value = initial_value def add_one(self, delta=1): with ShareCounter._lock: self._value += delta def sub_one(self, delta=1): with ShareCounter._lock: self.add_one(-delta)
这份代码中只有一个做用于整个类的锁,它被全部的类实例所共享,再也不将所绑定在某个实例的可变状态上,如今这个锁是用来同步类中的方法的。对于其余标准锁不一样的是,对于已经持有了该锁的方法能够调用一样使用了这个锁的其余方法(参考sub_one())。
这个实现的特色是,不管建立了多少counter实例,这些实例共有同一把锁。所以,当有大量counter出现时,这种方法堆内存的使用效率要高不少。可是可能存在的缺点是在使用了大量线程且须要频繁更新counter中的数据时,这么作会出现锁争用的状况。
另一种同步原语semaphore,是一种基于共享计数器的同步原语。若是计数器非0,那么with语句会递减计数器而且容许线程继续执行。当with语句块结束后,会将计数器递增。若是计数器为0,那么执行过程会被阻塞,直到由另一个线程来递增计数器为止。因为信号量的实现更为复杂,这会对程序带来必定的负面影响。除了简单地加锁功能外,信号量对象对于那些设计在线程间发送信号或者须要实现节流处理的应用中更加有用,例如限制并发总数:svg
from threading import Semaphore import urllib.request _fetch_url_sema = Semaphore(5) def fetch_url(url): with _fetch_url_sema: return urllib.request.urlopen(url)