Python第十二天

Parallel Programming 是一门CS系的课程,主要讲授如何针对单机多CPU内核(真*多线程)以及computer cluster 编程,以充分利用计算资源,提高程序性能。一般都会以MPI为例。水平上延展一点,还包括对GPU编程(一般都讲CUDA)。垂直上延展,就是distributed programming 分布式编程,一般会讲Hadoop和Spark。

主要应用领域包括科学计算,大数据分析,游戏开发,操作系统,数据库,web server等等。基本上单核CPU搞不定的计算任务,就得用parallel/distributed programming来搞定。
并行编程模型:

  • 数据并行(Data Parallel)模型:将相同的操作同时作用于不同数据,只需要简单地指明执行什么并行操作以及并行操作对象。该模型反映在图一中即是,并行同时在主线程中拿取数据进行处理,并线程执行相同的操作,然后计算完成后合并结果。各个并行线程在执行时互不干扰。

  • 消息传递(Message Passing)模型:各个并行执行部分之间传递消息,相互通讯。消息传递模型的并行线程在执行时会传递数据,可能一个线程运行到一半的时候,它所占用的数据或处理结果就要交给另一个线程处理,这样,在设计并行程序时会给我们带来一定麻烦。该模型一般是分布式内存并行计算机所采用方法,但是也可以适用于共享式内存的并行计算机。
    起步

queue 模块提供适用于多线程编程的先进先出(FIFO)数据结构。因为它是线程安全的,所以多个线程很轻松地使用同一个实例。

源码分析

先从初始化的函数来看:

class Queue:
def init(self, maxsize=0):
# 设置队列的最大容量
self.maxsize = maxsize
self._init(maxsize)

    # 线程锁,互斥变量
    self.mutex = threading.Lock()
    # 由锁衍生出三个条件变量
    self.not_empty = threading.Condition(self.mutex)
    self.not_full = threading.Condition(self.mutex)
    self.all_tasks_done = threading.Condition(self.mutex)

    self.unfinished_tasks = 0

def _init(self, maxsize):
    # 初始化底层数据结构
    self.queue = deque()

从这初始化函数能得到哪些信息呢?首先,队列是可以设置其容量大小的,并且具体的底层存放元素的它使用了 collections.deque() 双端列表的数据结构,这使得能很方便的做先进先出操作。这里还特地抽象为 _init 函数是为了方便其子类进行覆盖,允许子类使用其他结构来存放元素(比如优先队列使用了 list)。

然后就是线程锁 self.mutex ,对于底层数据结构 self.queue 的操作都要先获得这把锁;再往下是三个条件变量,这三个 Condition 都以 self.mutex 作为参数,也就是说它们共用一把锁;从这可以知道诸如 with self.mutex 与 with self.not_empty 等都是互斥的。

基于这些锁而做的一些简单的操作:

class Queue:

def qsize(self):
# 返回队列中的元素数
with self.mutex:
return self._qsize()

def empty(self):
    # 队列是否为空
    with self.mutex:
        return not self._qsize()

def full(self):
    # 队列是否已满
    with self.mutex:
        return 0 < self.maxsize <= self._qsize()

def _qsize(self):
    return len(self.queue)

这个代码片段挺好理解的,无需分析。

作为队列,主要得完成入队与出队的操作,首先是入队:

class Queue:

def put(self, item, block=True, timeout=None):
with self.not_full: # 获取条件变量not_full
if self.maxsize > 0:
if not block:
if self._qsize() >= self.maxsize:
raise Full # 如果 block 是 False,并且队列已满,那么抛出 Full 异常
elif timeout is None:
while self._qsize() >= self.maxsize:
self.not_full.wait() # 阻塞直到由剩余空间
elif timeout < 0: # 不合格的参数值,抛出ValueError
raise ValueError("‘timeout’ must be a non-negative number")
else:
endtime = time() + timeout # 计算等待的结束时间
while self._qsize() >= self.maxsize:
remaining = endtime - time()
if remaining <= 0.0:
raise Full # 等待期间一直没空间,抛出 Full 异常
self.not_full.wait(remaining)
self._put(item) # 往底层数据结构中加入一个元素
self.unfinished_tasks += 1
self.not_empty.notify()

def _put(self, item):
    self.queue.append(item)

尽管只有二十几行的代码,但这里的逻辑还是比较复杂的。它要处理超时与队列剩余空间不足的情况,具体几种情况如下:

如果 block 是 False,忽略timeout参数
若此时队列已满,则抛出 Full 异常;
若此时队列未满,则立即把元素保存到底层数据结构中;

如果 block 是 True
若 timeout 是 None 时,那么put操作可能会阻塞,直到队列中有空闲的空间(默认);
若 timeout 是非负数,则会阻塞相应时间直到队列中有剩余空间,在这个期间,如果队列中一直没有空间,抛出 Full 异常;

处理好参数逻辑后,,将元素保存到底层数据结构中,并递增unfinished_tasks,同时通知 not_empty ,唤醒在其中等待数据的线程。

出队操作:

class Queue:

def get(self, block=True, timeout=None):
with self.not_empty:
if not block:
if not self._qsize():
raise Empty
elif timeout is None:
while not self._qsize():
self.not_empty.wait()
elif timeout < 0:
raise ValueError("‘timeout’ must be a non-negative number")
else:
endtime = time() + timeout
while not self._qsize():
remaining = endtime - time()
if remaining <= 0.0:
raise Empty
self.not_empty.wait(remaining)
item = self._get()
self.not_full.notify()
return item

def _get(self):     
    return self.queue.popleft()

get() 操作是 put() 相反的操作,代码块也及其相似,get() 是从队列中移除最先插入的元素并将其返回。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值