哗啦啦Python之路 - 线程,进程,协程

1. 线程锁

如果不控制多个线程对同一资源进行访问的话,会对数据造成破坏,使得线程运行的结果不可预期。因此要引进线程锁。

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。 互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将 资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

未引入锁前:

import threading
import time

NUM = 0
class MyThread(threading.Thread):
    def run(self):
        global NUM
        NUM += 1
        time.sleep(0.5)
        msg = self.name+‘ set num to ‘+str(NUM)
        print(msg)
if __name__ == ‘__main__‘:
    for i in range(5):
        t = MyThread()
        t.start()

out:
Thread-5 set num to 2
Thread-3 set num to 3
Thread-2 set num to 5
Thread-1 set num to 5
Thread-4 set num to 4

引入锁之后:

import threading
import time

NUM = 0
class MyThread(threading.Thread):
    def run(self, l):
        l.acquire()
        global NUM
        NUM += 1
        time.sleep(0.5)
        msg = self.name+‘ set num to ‘+str(NUM)
        l.release()
        print(msg)
if __name__ == ‘__main__‘:
    lock = threading.Lock()
    for i in range(5):
        t = MyThread()
        t.run(lock)

out:
Thread-1 set num to 1
Thread-2 set num to 2
Thread-3 set num to 3
Thread-4 set num to 4
Thread-5 set num to 5

加线程锁

可 以看出,加了线程锁之后,尝试任何次数的执行,最终结果都是一样的,消除了线程不安全的隐患。过程是这样的:我们先建立了一个 threading.Lock类对象lock,在run方法里,我们使用lock.acquire()获得了这个锁。此时,其他的线程就无法再获得该锁 了,他们就会阻塞在“l.acquire()”这里,直到锁被另一个线程释放l.release()。

上边的锁机制,叫做“互斥锁”。同一时间仅允许一个线程进行更改数据。而信号量(Semaphore)是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

信号量:

import threading,time

def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s" %n)
    semaphore.release()

if __name__ == ‘__main__‘:

    num= 0
    semaphore  = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()

如果多个线程要调用多个现象,而A线程调用A锁占用了A对象,B线程调用了B锁占用了B对象,A线程不能调用B对象,B线程不能调用A对象,于是一直等待。这就造成了线程“死锁”
Threading模块中,也有一个类,RLock,称之为可重入锁。该锁对象内部维护着一个Lock和一个counter对象。counter对象记
录了acquire的次数,使得资源可以被多次require。最后,当所有RLock被release后,其他线程才能获取资源。在同一个线程
中,RLock.acquire可以被多次调用,利用该特性,可以解决部分死锁问题

解决死锁问题:

class _RLock:
    """This class implements reentrant lock objects.

    A reentrant lock must be released by the thread that acquired it. Once a
    thread has acquired a reentrant lock, the same thread may acquire it
    again without blocking; the thread must release it once for each time it
    has acquired it.

    """

    def __init__(self):
        self._block = _allocate_lock()
        self._owner = None
        # 初始化一个计数器
        self._count = 0

    def __repr__(self):
        owner = self._owner
        try:
            owner = _active[owner].name
        except KeyError:
            pass
        return "<%s %s.%s object owner=%r count=%d at %s>" % (
            "locked" if self._block.locked() else "unlocked",
            self.__class__.__module__,
            self.__class__.__qualname__,
            owner,
            self._count,
            hex(id(self))
        )

    def acquire(self, blocking=True, timeout=-1):
        """Acquire a lock, blocking or non-blocking.

        When invoked without arguments: if this thread already owns the lock,
        increment the recursion level by one, and return immediately. Otherwise,
        if another thread owns the lock, block until the lock is unlocked. Once
        the lock is unlocked (not owned by any thread), then grab ownership, set
        the recursion level to one, and return. If more than one thread is
        blocked waiting until the lock is unlocked, only one at a time will be
        able to grab ownership of the lock. There is no return value in this
        case.

        When invoked with the blocking argument set to true, do the same thing
        as when called without arguments, and return true.

        When invoked with the blocking argument set to false, do not block. If a
        call without an argument would block, return false immediately;
        otherwise, do the same thing as when called without arguments, and
        return true.

        When invoked with the floating-point timeout argument set to a positive
        value, block for at most the number of seconds specified by timeout
        and as long as the lock cannot be acquired.  Return true if the lock has
        been acquired, false if the timeout has elapsed.

        """
        me = get_ident()
        if self._owner == me:
            # 每次调用acquire,计数器加1
            self._count += 1
            return 1
        rc = self._block.acquire(blocking, timeout)
        if rc:
            self._owner = me
            self._count = 1
        return rc

    __enter__ = acquire

    def release(self):
        """Release a lock, decrementing the recursion level.

        If after the decrement it is zero, reset the lock to unlocked (not owned
        by any thread), and if any other threads are blocked waiting for the
        lock to become unlocked, allow exactly one of them to proceed. If after
        the decrement the recursion level is still nonzero, the lock remains
        locked and owned by the calling thread.

        Only call this method when the calling thread owns the lock. A
        RuntimeError is raised if this method is called when the lock is
        unlocked.

        There is no return value.

        """
        if self._owner != get_ident():
            raise RuntimeError("cannot release un-acquired lock")
        # 每次调用release,计数器减1
        self._count = count = self._count - 1
        if not count:
            self._owner = None
            self._block.release()

    def __exit__(self, t, v, tb):
        self.release()

    # Internal methods used by condition variables

    def _acquire_restore(self, state):
        self._block.acquire()
        self._count, self._owner = state

    def _release_save(self):
        if self._count == 0:
            raise RuntimeError("cannot release un-acquired lock")
        count = self._count
        self._count = 0
        owner = self._owner
        self._owner = None
        self._block.release()
        return (count, owner)

    def _is_owned(self):
        return self._owner == get_ident()

python3中Rlock的实现代码

死锁发生的条件:

  • 互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
  • 请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
  • 不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
  • 环 路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,...pn},进程p0(或线程)等待p1占用的资 源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等 待)

活锁:

活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

2. 队列

Python中的队列,常用的包括以下四种:1.先进先出队列  2.后进先出队列. 3.优先级队列  4.双向队列。这四种队列的使用,在queue模块中已经定义好了,具体使用如下。

1) 先进先出

import queue

# 定义队列最大长度
q = queue.Queue(maxsize=5)
# q.empty() 判断队列是否为空
print(‘if q is empty: ‘, q.empty())
# 向队列中push元素
q.put(1)
q.put(2)
q.put(3)
q.put(4)
# 判断队列长度是否达到上限
print(‘if q is full:‘, q.full())
print(‘queue size :‘ , q.qsize())
q.put(5)
print(‘if q is full:‘, q.full())
# 向队列中put数据,如果队列满,则立即抛出异常,不阻塞
q.put_nowait(6)
# 向队列中put数据,默认为block为True,表示阻塞,timeout表示最大等待时间,如果这段时间内put失败,则抛出异常
q.put(7, block=True, timeout=2)
print(‘if q is empty: ‘, q.empty())
# 返回当前队列元素个数
print(‘queue size :‘ , q.qsize())
# 从队列中获取单个数据
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# q.get(block, timeout) 默认block为True,表示是否阻塞。timeout表示阻塞最长时间,如果这段时间内无法获取元素,则抛出异常
print(q.get(block=True, timeout=2))
# block=False时,如果get异常,立即抛出错误
print(q.get(block=False))
# 从队列中获取元素,如果获取异常则抛出异常,不阻塞。
# print(q.get_nowait())
print(‘if q is empty: ‘, q.empty())

queue.Queue()用法

2) 后进先出

import queue

q = queue.LifoQueue()
q.put(1)
q.put(2)
q.put(3)
print(‘first get: ‘, q.get())
print(‘second get: ‘, q.get())
print(‘third get: ‘, q.get())

out:
first get:  3
second get:  2
third get:  1

queue.LifoQueue后进先出队列

3) 优先对级

import queue

q1 = queue.PriorityQueue() # 如果优先级相同,谁先放进去,先取出谁
q1.put((1, ‘alex1‘))
q1.put((2, ‘alex2‘))
q1.put((1, ‘alex3‘))

print(q1.get())
print(q1.get())
print(q1.get())

out:
(1, ‘alex1‘)
(1, ‘alex3‘)
(2, ‘alex2‘)

queue.PriorityQueue()优先级队列

4) 双向队列

import queue

q2 = queue.deque()
q2.append(1)
q2.append(2) # 从右侧增加数据
q2.append(3)
q2.appendleft(4) # 从左侧增加数据

q2.extend([5,6]) # 从右侧增加一个可迭代对象数据
q2.extendleft([7]) # 从左侧增加一个可迭代对象数据

print(q2)
q2.pop()  # 从右侧取数据
q2.popleft() # 从左侧取数据
print(q2)

out:
deque([7, 4, 1, 2, 3, 5, 6])
deque([4, 1, 2, 3, 5])

queue.deque()双向队列

9. 生产者消费者模型

有一个或多个生产者生产某种类型的数据,并放置在缓冲区(可以是数 组也可以是队列等数据结构)中;有一个消费者可以从缓冲区中取数据,每次取一项;系统保 证避免对缓冲区的重复操作,也就是说在任何时候只有一个主体(生产者或消费者)可以访问缓冲区。问题要确保缓冲区不溢出,即当缓冲区满时,生成者不会继续 向其中添加数据;当缓冲区空时,消费者不会从中移走数据。该模型可以解决:阻塞问题和程序解耦。下面是一个吃包子和做包子的案例代码:

import queue
import threading
import time

q = queue.Queue(20)

def productor(arg):
    while True:
        q.put(str(arg) + ‘号厨师弄出来的包子‘)

def consumer(arg):
    while True:
        print( arg, q.get())
        time.sleep(2)

for i in range(3):
    t = threading.Thread(target=productor, args=(i,))
    t.start()

for j in range(20):
    t = threading.Thread(target=consumer, args=(j,))
    t.start()

生产者、消费者模型案例代码

生产者的工作是产生一块数据,放到buffer中,如此循环。与此同时,消费者在消耗这些数据(例如从buffer中把它们移除),每次一块。这里的关键词是“同时”。所以生产者和消费者是并发运行的,我们需要对生产者和消费者做线程分离。假设这样一种情况,生产者负责往队列中存数据,消费者负责从队列中取数据。这两个线程同时运行,会存在这样一种情况:在某一时间点,消费者把所有东西消耗完毕而生产者还在挂起(sleep)。消费者尝试继续进行消耗,但此时队列为空,出现异常。我们把这个实现作为错误行为(wrong behavior)。测试代码如下:

from threading import Thread, Lock
import time
import random

queue = []
lock = Lock()

class ProducerThread(Thread):
  def run(self):
    nums = range(5) #Will create the list [0, 1, 2, 3, 4]
    global queue
    while True:
      num = random.choice(nums) #Selects a random number from list [0, 1, 2, 3, 4]
      lock.acquire()
      queue.append(num)
      print("Produced", num)
      lock.release()
      time.sleep(random.random())

class ConsumerThread(Thread):
  def run(self):
    global queue
    while True:
      lock.acquire()
      if not queue:
        print("Nothing in queue, but consumer will try to consume")
        exit()
      num = queue.pop(0)
      print("Consumed", num)
      lock.release()
      time.sleep(random.random())

ProducerThread().start()
ConsumerThread().start()

out:
Produced 1
Consumed 1
Produced 0
Produced 4
Produced 1
Consumed 0
Consumed 4
Produced 2
Consumed 1
Consumed 2
Produced 3
Consumed 3
Nothing in queue, but consumer will try to consume

错误行为代码

那么,正确行为应该是怎样的呢?

当队列中没有任何数据的时候,消费者应该停止运行并等待 (wait),而不是继续尝试进行消耗。而当生产者在队列中加入数据之后,应该有一个渠 道去告诉(notify)消费者。然后消费者可以再次从队列中进行消耗,而IndexError不再出现。而条件(Condition)正是用来解决这一 问题。

3. 进程

python
中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非
常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的

转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

1) 基本使用

from multiprocessing import Process

def foo(arg):
    print(‘say hi‘,arg)

if __name__ == "__main__":

    for i in range(10):
        p = Process(target=foo,args=(i,))
        #p.daemon = True  # 等同于线程的threading.Thread.setDaemon
        p.start()
        #p.join()

out:
say hi 0
say hi 2
say hi 4
say hi 1
say hi 3
say hi 6
say hi 8
say hi 7
say hi 5
say hi 9

多进程的基本使用

2) 进程锁

当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。

同 样的,进程锁也包含了其他类型,包括RLock,Semaphore(用来控制对共享资源的访问数量,例如池的最大连接数)。Event。同 线程也是一样的。所属模块不同而 已:multiprocessing.Lock(),multiprocessing.RLock(),multiprocessing.Semaphore(n) ,multiprocessing.Event()。 具体使用案例可以查看http://www.cnblogs.com/kaituorensheng/p/4445418.html  或者将上边讲到的线程案例进行修改。

3) 进程间资源共享

默认情况下,进程间的数据是不可以进行共享的。但是可以通过以下三个方法进行数据共享:queues, Array, Manager.dict()

- queues

from multiprocessing import Process
from multiprocessing import queues
import multiprocessing

def foo(i,arg):
    arg.put(i)
    print(‘say hi‘,i,arg.qsize())

if __name__ == "__main__":
    li = queues.Queue(20,ctx=multiprocessing) # 源码中执行了ctx.lock
    for i in range(10):
        p = Process(target=foo,args=(i,li,))
        p.start()

out:
say hi 0 1
say hi 2 2
say hi 1 4
say hi 3 4
say hi 6 7
say hi 5 7
say hi 4 7
say hi 7 8
say hi 9 9
say hi 8 10

multiprocessing.queues

- Array(不常用)

数组:Array的特点:1.它的内存地址是连续的,而列表不是  2. 数组中元素的数据类型,在创建的时候就已经定义好了。3. 个数一定。在创建的时候就需要指定

from multiprocessing import Process
from multiprocessing import Array

def foo(i,arg):
    arg[i] = i + 100
    for item in arg:
        print(item)
    print(‘================‘)

if __name__ == "__main__":
    li = Array(‘i‘, 5) # 创建一个最大包含5个元素的数组
    for i in range(5):
        p = Process(target=foo,args=(i,li,))
        p.start()

out:
0
0
================
101
0
================
101
0
================
101
103
================
101
103
================

multiprocessing.Array

- Manager.dict(常用)

from multiprocessing import Process
from multiprocessing import Manager

def foo(i,arg):
    arg[i] = i + 100
    print(arg.values())

if __name__ == "__main__":
    obj = Manager()
    li = obj.dict()
    for i in range(5):
        p = Process(target=foo,args=(i,li,))
        #p.daemon = True
        p.start()
        p.join()
out:
[100]
[100, 101]
[100, 101, 102]
[100, 101, 102, 103]
[100, 101, 102, 103, 104]

Manager.dict实现数据共享

4. 进程池

进程池在multiprocessing模块中已经定义好,只需要直接使用即可。主要包含2个方法apply和apply_async

- 进程池的使用:

from multiprocessing import Pool
import time
def f1(arg):
    print(arg,‘b‘)
    time.sleep(5)
    print(arg,‘a‘)

if __name__ == "__main__":
    pool = Pool(5)
    # 定义30个任务
    for i in range(30):
        # pool.apply(func=f1,args=(i,))
        pool.apply_async(func=f1,args=(i,))

    # pool.close() # 等待所有的任务执行完毕后,修改进程状态为close。否则会阻塞到这里
    time.sleep(2)
    pool.terminate() # 立即终止全部子进程
    pool.join()  # 主进程在这里等待子进程全部执行完毕

out:
b
b
b
b
b  # 执行到一半已经被强制终止

Process finished with exit code 0

进程池的使用

apply(self, func, args=(), kwds={}) # 使用arg和kwds参数调用func函数,结果返回前会一直阻塞,这样就导致子进程会顺序执行,而不是并发执行

apply_async(self, func, args=(), kwds={}, callback=None,error_callback=None)#  apply()方法的一个变体,会返回一个结果对象。如果callback被指定,那么callback可以接收一个参数然后被调用,当结果准备好回调 时会调用callback,调用失败时,则用error_callback替换callback。 Callbacks应被立即完成,否则处理结果的线程会被阻塞。对比apply方法,该方法不会阻塞,类似线程的setDaemon

pool.close() # 阻止更多的任务提交到pool,待任务完成后,工作进程会退出

pool.terminate() # 不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。

pool.join()  # wait工作线程的退出,在调用join()前,必须调用close() or terminate()。这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。

参考:https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool

5. 协程

1) 概念

线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。

协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。

协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

2) 实现

协程的实现主要借助2个模块,greenlet和gevent(内部调用greenlet)

- gevent

import gevent
def fun1():
  print("www.baidu.com")  # 第一步
  gevent.sleep(0)
  print("end the baidu.com") # 第三步
def fun2():
  print("www.zhihu.com")  # 第二步
  gevent.sleep(0)
  print("end th zhihu.com") # 第四步
gevent.joinall([
  gevent.spawn(fun1),
  gevent.spawn(fun2),
])

gevent

- greenlet

import greenlet
def fun1():
  print("12") # 第一步
  gr2.switch()
  print("56")  # 第三步
  gr2.switch()
def fun2():
  print("34") # 第二步
  gr1.switch()
  print("78") # 第四步
gr1 = greenlet.greenlet(fun1)
gr2 = greenlet.greenlet(fun2)
gr1.switch()

greenlet
时间: 2024-10-05 05:00:21

哗啦啦Python之路 - 线程,进程,协程的相关文章

15.python并发编程(线程--进程--协程)

一.进程:1.定义:进程最小的资源单位,本质就是一个程序在一个数据集上的一次动态执行(运行)的过程2.组成:进程一般由程序,数据集,进程控制三部分组成:(1)程序:用来描述进程要完成哪些功能以及如何完成(2)数据集:是程序在执行过程中所需要使用的一切资源(3)进程控制块:用来记录进程外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志.3.进程的作用:是想完成多任务并发,进程之间的内存地址是相互独立的二.线程:1.定义:最小的执行单位,线程的出现是为了

Python并发编程(线程队列,协程,Greenlet,Gevent)

线程队列 线程之间的通信我们列表行不行呢,当然行,那么队列和列表有什么区别呢? queue队列 :使用import queue,用法与进程Queue一样 queue is especially useful in threaded programming when information must be exchanged safely between multiple threads. class queue.Queue(maxsize=0) #先进先出 import queue #不需要通过

Python的线程&amp;进程&amp;协程[0] -&gt; 基本概念

基本概念 / Basic Concept 0 简介与动机 / Why Multi-Thread/Multi-Process/Coroutine 在多线程(multithreaded, MT)编程出现之前,计算机程序的执行是由单个步骤序列组成的,该序列在主机的CPU中按照同步顺序执行.即无论任务多少,是否包含子任务,都要按照顺序方式进行. 然而,假定子任务之间相互独立,没有因果关系,若能使这些独立的任务同时运行,则这种并行处理方式可以显著提高整个任务的性能,这便是多线程编程. 而对于Python而

4月28日 python学习总结 线程与协程

一. 异步与回调机制 问题: 1.任务的返回值不能得到及时的处理,必须等到所有任务都运行完毕才能统一进行处理 2.解析的过程是串行执行的,如果解析一次需要花费2s,解析9次则需要花费18s 解决一: (线程实现异步,回调解析结果) from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import requests import os i

11 线程进程协程

1 线程 1.1 基本应用 1.1.1 标准线程(常用) import threading def f1(arg): print(arg) t = threading.Thread(target=f1, args=(123,)) t.start() 1.1.2 自定义线程 自定义线程类既threading.Thread流程,自定义run方法 import threading class MyThread(threading.Thread): #自定义类,继承threading.Thread类 d

线程 进程 协程

一.什么是线程? 线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元).它被包含在进程之中,是进程中的实际运作单位.一条线程指的是一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成.另外,线程是进程的中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源.一个线程可以创建和撤销另一

Python的线程&amp;进程&amp;协程[0] -&gt; 线程 -&gt; 多线程的建立与使用

常用的多线程功能实现 目录 生成线程的三种方法 单线程与多线程对比 守护线程的设置 1 生成线程的三种方法 三种方式分别为: 创建一个Thread实例,传给它一个函数 创建一个Thread实例,传给它一个可调用的类实例 派生Thread的子类,并创建子类的实例 # There are three ways to create a thread # The first is create a thread instance, and pass a function # The second one

Python的线程&amp;进程&amp;协程[0] -&gt; 线程 -&gt; 多线程锁的使用

锁与信号量 目录 添加线程锁 锁的本质 互斥锁与可重入锁 死锁的产生 锁的上下文管理 信号量与有界信号量 1 添加线程锁 由于多线程对资源的抢占顺序不同,可能会产生冲突,通过添加线程锁来对共有资源进行控制. 1 import atexit 2 from random import randrange 3 from threading import Thread, Lock, current_thread # or currentThread 4 from time import ctime, s

Python的线程&amp;进程&amp;协程[1] -&gt; 线程 -&gt; 多线程的控制方式

多线程的控制方式 目录 唤醒单个线程等待 唤醒多个线程等待 条件函数等待 事件触发标志 函数延迟启动 设置线程障碍 1 唤醒单个线程等待 Condition类相当于一把高级的锁,可以进行一些复杂的线程同步控制.一般Condition内部都有一把内置的锁对象(默认为RLock),对于Condition的使用主要有以下步骤: 建立两个线程对象,及Condition对象; 线程1首先获取Condition的锁权限,acquire(); 线程1执行需要完成的任务后,调用等待wait(),此时,线程1会阻