阅读Python官网文档对threading.Thread线程的介绍

Python线程模块threading:https://docs.python.org/zh-cn/3/library/threading.html

threading.Thread

1、Python的threading模块定义了许多类,该模块的设计基于Java的线程模型。但是,在Java里面,锁和条件变量是每个对象的基础特性,而在Python里面,这些被独立成了单独的对象(threading.Lock()锁,threading.Condition()条件变量)。Python 的 Thread 类只是 Java 的 Thread 类的一个子集;目前还没有优先级,没有线程组,线程还不能被销毁、停止、暂停、恢复或中断。Java 的 Thread 类的静态方法在实现时会映射为模块级函数。

2、threading.Thread类表示运行在一个独立控制的线程中的一项活动。有两种方式指定活动:传递可调用对象给threading.Thread构造函数,也就是threading.Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)中的target。或在threading.Thread的子类中覆盖run()方法。子类出了构造方法外不该覆盖其他方法。换句话说,子类只能覆盖threading.Thread的__init__()方法(都继承了当然要覆盖并传递调用父类构造)和run()方法。

对my_thread1 = threading.Thread(target=my_func)的用法,没有继承threading.Thread类,则threading.Thread.run()方法默认调用self.target()。如果继承了threading.Thread类,当然可以在覆盖的DerivedThread.__init__(target=my_func,args=my_args,kwargs=my_kwargs)构造器中传递target并首先调用threading.Thread.__init__(target=myfunc,args=my_args,kwargs=my_kwargs),也有机会重写run方法。

标准的(即thread.Thread.run) run() 方法会对作为 target 参数传递给该对象构造器的可调用对象(如果存在)发起调用,并附带从 args 和 kwargs 参数分别获取的位置和关键字参数。

3、当线程对象一但被创建,其活动一定会因调用线程的 start() 方法开始。这会在独立的控制线程(也就是启动的子线程)调用 run() 方法。

4、一旦线程活动开始,该线程会被认为是 '存活的' 。当它的 run() 方法终结了(不管是正常的还是抛出未被处理的异常),就不是'存活的'。 is_alive() 方法用于检查线程是否存活。

>>> cur_thread = threading.current_thread()
>>> cur_thread.is_alive()
True

5、其他线程(相对的主线程)可以调用一个线程(子线程引用)的 join() 方法。这会阻塞调用该方法的线程(相对的主线程),直到被调用 join() 方法的线程(子线程)终结。

6、线程有名字。名字可以传递给构造函数,也就是threading.Thread(group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None)中的name,也可以通过 name 属性读取或者修改。

>>> cur_thread.name
'MainThread'

7、如果 run() 方法引发了异常,则会调用 threading.excepthook() 来处理它。 在默认情况下,threading.excepthook() 会静默地忽略 SystemExit

8、一个线程可以被标记成一个“守护线程”。 这个标识的意义是,当剩下的线程都是守护线程时,整个 Python 程序将会退出。 初始值继承于创建线程。 这个标识可以通过 daemon 特征属性或者 daemon 构造器参数来设置。

注解:守护线程在程序关闭时会突然关闭。他们的资源(例如已经打开的文档,数据库事务等等)可能没有被正确释放。如果你想你的线程正常停止,设置他们成为非守护模式并且使用合适的信号机制,例如: Event

守护线程我理解为比如监视线程、消费者线程,当生产者线程都结束时,剩下的监视线程、消费者线程都没有存在的必要,可以立即结束了。

有个 "主线程" 对象;这对应Python程序里面初始的控制线程。它不是一个守护线程。

>>> cur_thread.daemon
False
>>> cur_thread.daemon=True
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/haypin/miniconda3/lib/python3.9/threading.py", line 1127, in daemon
    raise RuntimeError("cannot set daemon status of active thread")
RuntimeError: cannot set daemon status of active thread

9、"虚拟线程对象" 是可以被创建的。这些是对应于“外部线程”的线程对象(我理解是对外部线程的引用,但Python对其封装的方法有限,不能监测外来线程的终结),它们是在线程模块外部启动的控制线程,例如直接来自C代码。虚拟线程对象功能受限;他们总是被认为是存活的和守护模式,不能被 join() 。因为无法检测外来线程的终结,它们永远不会被删除。

class ThreadWithReturnValue(threading.Thread):
  def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, Verbose=None):
    threading.Thread.__init__(self, group, target, name, args, kwargs, Verbose)
    self._return = None
  def run(self):
    if self._Thread__target is not None:
      self._return = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
  def join(self, timeout):
    threading.Thread.join(self, timeout)
    return self._return

10、class threading.Thread(group=Nonetarget=Nonename=Noneargs=()kwargs={}*daemon=None)

调用这个构造函数时,必需带有关键字参数。参数如下:

group 应该为 None;为了日后扩展 ThreadGroup 类实现而保留。

target 是用于 run() 方法调用的可调用对象。默认是 None,表示不需要调用任何方法。

name 是线程名称。默认情况下,由 "Thread-N" 格式构成一个唯一的名称,其中 N 是小的十进制数。

args 是用于调用目标函数的参数元组(可迭代对象,列表、元组)。默认是 ()

kwargs 是用于调用目标函数的关键字参数字典。默认是 {}

如果不是 Nonedaemon 参数将显式地设置该线程是否为守护模式。 如果是 None (默认值),线程将继承当前线程的守护模式属性。

如果子类型重载了构造函数,它一定要确保在做任何事前,先发起调用基类构造器(Thread.__init__())

10.1 start()

开始线程活动。

它在一个线程里最多只能被调用一次。它安排对象的 run() 方法在一个独立的控制进程中调用。

如果同一个线程对象中调用这个方法的次数大于一次,会抛出 RuntimeError 。

10.2 run()

代表线程活动的方法。

你可以在子类型里重载这个方法。 标准的 run() 方法会对作为 target 参数传递给该对象构造器的可调用对象(如果存在)发起调用,并附带从 args 和 kwargs 参数分别获取的位置和关键字参数

10.3  join(timeout=None)

等待,直到线程终结。这会阻塞调用这个方法的线程(相对主线程),直到被调用 join() 的线程终结 -- 不管是正常终结还是抛出未处理异常 -- 或者直到发生超时,超时选项是可选的。

当 timeout 参数存在而且不是 None 时,它应该是一个用于指定操作超时的以秒为单位的浮点数(或者分数)。因为 join() 总是返回 None ,所以你一定要在 join() 后调用 is_alive() 才能判断是否发生超时 -- 如果线程仍然存活,则 join() 超时(调用join()处因超时而停止阻塞,才能执行到后面的is_alive(),此时线程大概率还活着,一旦活着那肯定是超时了)。

当 timeout 参数不存在或者是 None ,这个操作会阻塞直到线程终结。

一个线程可以被 join() 很多次(指同一主线程的多个函数都超时阻塞同一个子线程,或多个线程阻塞等待同一个线程,一个线程的多个函数非超时阻塞同一个子线程是没有意义的)

如果尝试加入当前线程会导致死锁(同一线程自我阻塞、两个线程相互阻塞,后者指:主线程在构造子线程时将自身引用传递给子线程,子线程的线程函数中有阻塞等待传入的父线程,然后父线程再阻塞等待这个子线程), join() 会引起 RuntimeError 异常。如果尝试 join() 一个尚未开始的线程,也会抛出相同的异常

>>> cur_thread.join()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/haypin/miniconda3/lib/python3.9/threading.py", line 1030, in join
    raise RuntimeError("cannot join current thread")
RuntimeError: cannot join current thread

10.4 name

只用于识别的字符串。它没有语义。多个线程可以赋予相同的名称。 初始名称由构造函数设置。

10.5 ident

这个线程的 '线程标识符',如果线程尚未开始则为 None 。这是个非零整数。参见 get_ident() 函数。当一个线程退出而另外一个线程被创建,线程标识符会被复用。即使线程退出后,仍可得到标识符(表示这个线程曾经的标识符)

10.6 native_id

此线程的原生集成线程 ID。 这是一个非负整数,或者如果线程还未启动则为 None。 请参阅 get_native_id() 函数。 这表示线程 ID (TID) 已被 OS (内核) 赋值给线程。 它的值可能被用来在全系统范围内唯一地标识这个特定线程(直到线程终结,在那之后该值可能会被 OS 回收再利用)。

注解:类似于进程 ID,线程 ID 的有效期(全系统范围内保证唯一)将从线程被创建开始直到线程被终结。

可用性: 需要 get_native_id() 函数。

3.8 新版功能.

10.7  is_alive()

返回线程是否存活。

当 run() 方法刚开始直到 run() 方法刚结束,这个方法返回 True 。模块函数 enumerate() 返回包含所有存活线程的列表。

10.8  daemon

一个表示这个线程是(True)否(False)守护线程的布尔值。一定要在调用 start() 前设置好,不然会抛出 RuntimeError 。初始值继承于创建线程;主线程不是守护线程,因此主线程创建的所有线程默认都是 daemon = False

当没有存活的非守护线程时,整个Python程序才会退出。

>>> func1 = lambda :print('heihei')
>>> func1()
heihei
>>> thread1 = threading.Thread(target=func1,daemon=True)
>>> thread1.daemon
True
>>> thread1.start()
heihei
>>> thread1.is_alive()
False
>>> thread1.native_id
526333
>>> thread1.ident
123145454923776
>>> thread1.name
'Thread-1'

锁对象

原始锁是一个在锁定时不属于特定线程的同步基元组件(而thread.Lock()对象被某个线程acquire()获得后就独属于这个线程,其他线程尝试acquire()将阻塞直到锁被release()释放)。在Python中,它是能用的最低级的同步基元组件,由 _thread 扩展模块直接实现。

原始锁处于 "锁定" 或者 "非锁定" 两种状态之一。它被创建时为非锁定状态。它有两个基本方法, acquire() 和 release() 。当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError  异常。

锁同样支持 上下文管理协议(LEGB)。

当多个线程在 acquire() 等待状态转变为未锁定被阻塞,然后 release() 重置状态为未锁定时,只有一个线程能继续执行;至于哪个等待线程继续执行没有定义,并且会根据实现而不同。

所有方法的执行都是原子性的(必须的,同样的还有queue.Queue的方法都是原子性的)。

11  class threading.Lock

实现原始锁对象的类。一旦一个线程获得一个锁,会阻塞随后尝试获得锁的线程,直到它被释放;任何线程都可以释放它。

需要注意的是 Lock 其实是一个工厂函数(类),返回平台支持的具体锁类中最有效的版本的实例

11.1 acquire(blocking=Truetimeout=-1)

可以阻塞或非阻塞地获得锁

当调用时参数 blocking 设置为 True (缺省值),阻塞直到锁被释放,然后将锁锁定并返回 True 。

在参数 blocking 被设置为 False 的情况下调用,将不会发生阻塞。如果调用时 blocking 设为 True 会阻塞,并立即返回 False(阻塞并立即返回False,并且最多只执行返回False这一步,不会继续执行更多语句,见下 ;否则,将锁定并返回 True

# Test4
locker = threading.Lock()
def func1(time_span=10.0):
    locker.acquire()
    pre_time = time.mktime(time.localtime())
    while True:
        if time.mktime(time.localtime())-pre_time >time_span:
            break
        print('thread1 runing...')
        time.sleep(1)
    print('thread1 exit')
    locker.release()
if __name__ == '__main__':
    thread1 = threading.Thread(target=func1,args=([3.0]))
    thread1.start()
    time.sleep(1)
    print('main-Thread acquire locker return %s'%(locker.acquire(blocking=True)))
    print('main-Thread acquire locker finally')
    thread1.join()
    print('main-thread1 exit')

执行结果:

haypin@HaypinsMBP  ~/catkin_ws/src/lift_strategyBak   master ±  python test_thread.py
thread1 runing...
thread1 runing...
thread1 runing...
thread1 runing...
thread1 exit
main-Thread acquire locker return True
main-Thread acquire locker finally
main-thread1 exit

子线程率先取得锁后,主线程执行locker.acquire()会发现锁已被其他线程获取,则立即返回False,但不继续执行更多语句,而是立即阻塞。直到子线程释放锁,主线程取得锁并返回True后才继续执行主线程下文的语句

if __name__ == '__main__':
    thread1 = threading.Thread(target=func1,args=([3.0]))
    thread1.start()
    time.sleep(1)
    print('main-Thread acquire locker return %s'%(locker.acquire(blocking=False)))
    # print('main-Thread acquire locker finally')
    thread1.join()
    print('main-thread1 exit')
执行结果:
haypin@HaypinsMBP  ~/catkin_ws/src/lift_strategyBak   master ±  python test_thread.py
thread1 runing...
main-Thread acquire locker return False
thread1 runing...
thread1 runing...
thread1 runing...
thread1 exit
main-thread1 exit

当浮点型 timeout 参数被设置为正值调用时,只要无法获得锁,将最多阻塞 timeout 设定的秒数。timeout 参数被设置为 -1 时将无限等待。当 blocking 为 false 时,timeout 指定的值将被忽略。

如果成功获得锁,则返回 True,否则返回 False (例如发生 超时 的时候)。

在 3.2 版更改: 新的 timeout 形参。

在 3.2 版更改: 现在如果底层线程实现支持,则可以通过POSIX上的信号中断锁的获取。

11.2 release()

释放一个锁。这个方法可以在任何线程中调用,不单指获得锁的线程(哪个线程都可能获得锁)。

当锁被锁定,将它重置为未锁定,并返回。如果其他线程正在等待这个锁解锁而被阻塞,只允许其中一个允许。

在未锁定的锁调用时,会引发 RuntimeError 异常。

没有返回值(要么成功,要么释放未锁定的锁触发RuntimeError异常)

11.3 locked()

如果获得了锁则返回真值。

递归锁对象

重入锁是一个可以被同一个线程多次获取的同步基元组件。在内部,它在基元锁的锁定/非锁定状态上附加了 "所属线程" 和 "递归等级" 的概念。在锁定状态下,某些线程拥有锁 ; 在非锁定状态下, 没有线程拥有它。

若要锁定锁,线程调用其 acquire() 方法;一旦线程拥有了锁,方法将返回。若要解锁,线程调用 release() 方法。 acquire()/release() 对可以嵌套(《C++ Concurrency in Action》有篇幅更多的介绍,递归锁不建议使用!);只有最终 release() (最外面一对的 release() ) 将锁解开,才能让其他线程继续处理 acquire() 阻塞。

递归锁也支持 上下文管理协议

 

递归锁可能的使用场景还比如:递归调用的函数,如果是std::mutex或threading.Lock,那递归加锁会引发未定义的错误,此时必须使用std::recursive_mutex、threading.RLock。 

12 class threading.RLock

此类实现了重入锁对象。重入锁必须由获取它的线程释放(顺序获取锁,逆序释放锁,类似于顺序构造逆序析构)。一旦线程获得了重入锁,同一个线程再次获取它将不阻塞(但会修改递归锁的递归等级属性);线程必须在每次获取它时释放一次。

需要注意的是 RLock 其实是一个工厂函数,返回平台支持的具体递归锁类中最有效的版本的实例。

12.1 acquire(blocking=Truetimeout=-1)

可以阻塞或非阻塞地获得锁。

当无参数调用时: 如果这个线程已经拥有锁,递归级别增加一,并立即返回。否则,如果其他线程拥有该锁,则阻塞至该锁解锁。一旦锁被解锁(不属于任何线程),则抢夺所有权,(抢到的线程)设置递归等级为一,并返回。如果多个线程被阻塞,等待锁被解锁,一次只有一个线程能抢到锁的所有权。在这种情况下,没有返回值(获得锁后执行下步语句,获取锁失败就返回False并阻塞,不继续执行下步语句,见上)。

当发起调用时将 blocking 参数设为真值,则执行与无参数调用时一样的操作(无参数默认blocking=True),然后返回 True(阻塞直到获得锁返回True并执行下步)

当发起调用时将 blocking 参数设为假值,则不进行阻塞。 如果一个无参数调用将要阻塞,则立即返回 False(只是返回False,返回False后立即阻塞,不继续执行下步);在其他情况下,执行与无参数调用时一样的操作(阻塞),然后返回 True(获得锁,阻塞结束,返回True并继续执行下文)

当发起调用时将浮点数的 timeout 参数设为正值时,只要无法获得锁,将最多阻塞 timeout 所指定的秒数。 如果已经获得锁则返回 True,如果超时则返回假值。

在 3.2 版更改: 新的 timeout 形参。

12.2  release()

释放锁,自减递归等级。如果减到零,则将锁重置为非锁定状态(不被任何线程拥有),并且,如果其他线程正被阻塞着等待锁被解锁,则仅允许其中一个线程继续。如果自减后,递归等级仍然不是零,则锁保持锁定,仍由调用线程拥有

20210725fhp:递归锁的递归等级很像读写锁的“加读锁增加读锁计数,释放读锁减读锁计数,读锁计数归零后紧接着时间序的锁排队队列的下个写锁上锁,写锁释放锁后紧接着时间序的锁排队队列的下个写锁或读锁上锁”

只有当前线程拥有锁才能调用这个方法。如果锁被释放后调用这个方法,会引起 RuntimeError 异常。

没有返回值。

条件对象

条件变量总是与某种类型的锁对象相关联,锁对象可以通过传入获得,或者在缺省的情况下自动创建(自动创建的锁岂不是随时都能获取到?从而不会因阻塞而释放锁后休眠)。当多个条件变量需要共享同一个锁时,传入一个锁很有用。锁是条件对象的一部分,你不必单独地跟踪它。

条件变量遵循 上下文管理协议 :使用 with 语句会在它包围的代码块内获取关联的锁(并在它包围的代码块结束后析构栈帧时释放锁)。条件变量的 acquire() 和 release() 方法也能调用关联锁的相关方法(条件变量的notify()方法将首先调用关联的锁的release()方法释放锁调用后,然后通知阻塞【休眠】的消费者线程苏醒获取锁后继续执行下文,也即消费者的wait()方法将首先调用关联的锁的acquire()方法获取锁)。

其它方法必须在持有关联的锁的情况下调用。 wait() 方法释放锁,然后阻塞【休眠】直到其它线程调用 notify() 方法或 notify_all() 方法唤醒它。一旦被唤醒, wait() 方法重新获取锁并返回。它也可以指定超时时间。

The notify() method wakes up one of the threads waiting for the condition variable, if any are waiting. The notify_all() method wakes up all threads waiting for the condition variable.

notify()方法唤醒等待条件变量的其中一个线程,如果有在等待的线程的话。notify_all()方法唤醒所有等待条件变量的线程。

注意: notify() 方法和 notify_all() 方法并不会释放锁,这意味着被唤醒的线程不会立即从它们的 wait() 方法调用中返回,而是会在调用了 notify() 方法或 notify_all() 方法的线程最终放弃了锁的所有权(条件变量的with代码块结束,或主动调用条件变量的release()方法——>导致关联的互斥量调用其release()方法释放锁)后返回

使用条件变量的典型编程风格是使用锁来同步对某些共享状态的访问(The typical programming style using condition variables uses the lock to synchronize access to some shared state; ),那些对状态的某些特定改变感兴趣的线程,它们重复调用 wait() 方法,直到看到所期望的改变发生;而对于修改状态的线程,它们将当前状态改变为可能是等待者所期待的新状态后,调用 notify() 方法或者 notify_all() 方法。例如,下面的代码是一个通用的无限缓冲区容量的生产者-消费者情形:

# Consume one item
with cv:    # 条件变量cv关联锁是为了对要读的item进行上锁
    while not an_item_is_available():
        cv.wait()
    get_an_available_item()

# Produce one item
with cv:    # 条件变量cv关联锁是为了对要写的item进行上锁
    make_an_item_available()
    cv.notify()

举个例子:

# Test6
# class Data:
#     data = False
# Flag = Data()    # 线程共享数据,
Flag = False    # 线程共享数据
locker2 = threading.Lock()
cv = threading.Condition(lock=locker2)
def consumer():
    with cv:    # 条件变量关联锁加读锁,锁共享数据Flag
        if not Flag:
        # while not Flag:
            print('consumer get data %s, will cv.wait(). timeI %s'%(Flag,time.mktime(time.localtime())))
            cv.wait()   # 共享数据为False时释放锁,阻塞,休眠;被唤醒后获得锁
            print('timeIII %s'%(time.mktime(time.localtime())))
        print('consumer consumed data %s, then exit'%(Flag))
def producer(time_span=5.0):
    now_time = time.mktime(time.localtime())
    while time.mktime(time.localtime()) - now_time <time_span:
        time.sleep(1)
    with cv:    # 加写锁,锁共享数据Flag,写共享数据后释放锁
        global Flag # 一定要声明LEGB才使Local的Flag = True是赋值而不是定义,所以放类里做类属性能避免声明Global
        Flag = True # 写共享数据,原子操作
        print('producer produced data %s, then cv.notify() and exit, timeII %s'%(Flag,time.mktime(time.localtime())))
        cv.notify()
        time.sleep(1)
        # 作用域析构,释放条件变量关联锁

输出:

haypin@HaypinsMBP  ~/catkin_ws/src/lift_strategyBak   master ±  python test_thread2.py
consumer get data False, will cv.wait(). timeI 1627271253.0
producer produced data True, then cv.notify() and exit, timeII 1627271256.0
timeIII 1627271257.0
consumer consumed data True, then exit

使用 while 循环检查所要求的条件成立与否是有必要的,因为 wait() 方法可能要经过不确定长度的时间后才会返回,而此时导致 notify() 方法调用的那个条件可能已经不再成立(比如说生产者线程在条件A达成的前提下继续生产条件B,然后在条件B达成后算作生产完毕并notify()唤醒休眠中的消费者线程,然而此时条件A可能按下葫芦起了瓢变成不满足了,导致唤醒的消费者线程自认为条件A、条件B都满足而进行读写共享数据,导致异常)。这是多线程编程所固有的问题。

# Test6
# class Data:
#     data = False
# Flag = Data()    # 线程共享数据,
Flag = False    # 线程共享数据
locker2 = threading.Lock()
cv = threading.Condition(lock=locker2)
def consumer():
    with cv:    # 条件变量关联锁加读锁,锁共享数据Flag
        # if not Flag:
        while not Flag:
            print('consumer get data %s, will cv.wait(). timeI %s'%(Flag,time.mktime(time.localtime())))
            cv.wait()   # 共享数据为False时释放锁,阻塞,休眠;被唤醒后获得锁
            print('timeIII %s'%(time.mktime(time.localtime())))
        print('consumer consumed data %s, then exit'%(Flag))
def producer(time_span=5.0):
    now_time = time.mktime(time.localtime())
    while time.mktime(time.localtime()) - now_time <time_span:
        time.sleep(1)
    with cv:    # 加写锁,锁共享数据Flag,写共享数据后释放锁
        global Flag # 一定要声明LEGB才使Local的Flag = True是赋值而不是定义,所以放类里做类属性能避免声明Global
        Flag = True # 写共享数据,原子操作
        print('producer produced data %s, then cv.notify() and exit, timeII %s'%(Flag,time.mktime(time.localtime())))
        cv.notify()
        time.sleep(1)
        # 作用域析构,释放条件变量关联锁

输出:

haypin@HaypinsMBP  ~/catkin_ws/src/lift_strategyBak   master ±  python test_thread2.py
consumer get data False, will cv.wait(). timeI 1627271419.0
producer produced data True, then cv.notify() and exit, timeII 1627271422.0
timeIII 1627271423.0
consumer consumed data True, then exit

 

解决办法就是:1、对生产者线程,notify()唤醒休眠的消费者线程之前,也就是确定条件B满足的时候,再次检查下作为前提的条件A是否依旧满足,如果条件A、B同时满足才notify(),如果条件B满足而条件A不满足,那goto跳转到对条件A操作的前一步,相当于递归调用生产者线程,此期间仍对条件变量-互斥量上锁;2、对休眠中的消费者线程,在被生产者线程唤醒后再次检查所有条件A、B是否满足,这一步可以放在cv.wait()方法中,也就是wait_for(A and B)。wait_for() 方法可自动化条件检查,并简化超时计算。

# Consume an item
with cv:
    cv.wait_for(an_item_is_available)
    get_an_available_item()

选择 notify() 还是 notify_all() ,取决于一次状态改变是只能被一个还是能被多个等待线程所用。例如在一个典型的生产者-消费者情形中,添加一个项目到缓冲区只需唤醒一个消费者线程。

另外,Bool、int、double、float、char、*简单数据结构的读写都是单步汇编语句,本身就是原子操作(要么已执行,要么未执行,不会被异步线程捕捉到“执行中”的状态),可以不用加锁同步。而数组、自定义类实例的读写往往不是单步汇编语句(可能被异步线程捕捉到“执行中”状态而读到正在被写的数据、或写正在被读的数据),需要加锁来同步


#Test7
class T(object):
    Flag = False    # 成员方法线程共享数据,Bool值的写操作是单步的汇编语句,是原子的,可以不用锁同步读写
    def consumer(self,time_span=3.0):
        while True:
            if self.Flag == False:
                print('consumer consuming')
                pre_time = time.mktime(time.localtime())
                while time.mktime(time.localtime()) - pre_time <time_span:
                    time.sleep(1)
                self.Flag = True
                print('consumer had consumed, will exit')
                break
            else:
                print('consumer had consumed, will exit')
                break
    def producer(self):
        while True:
            if self.Flag == False:
                print('producer confirm consumer had not consumed, will loop')
                time.sleep(1)
            else:
                print('producer confirm consumer had consumed, will exit')
                break
if __name__ == '__main__':
    obj = T()
    threads = []
    threads.append(threading.Thread(target=obj.consumer,args=[3.0]))
    threads.append(threading.Thread(target=obj.producer))
    for ithread in threads:
        ithread.start()

13 class threading.Condition(lock=None)

实现条件变量对象的类。一个条件变量对象允许一个或多个线程在被其它线程所通知之前进行等待。

如果给出了非 None 的 lock 参数,则它必须为 Lock 或者 RLock 对象,并且它将被用作底层锁。否则,将会创建新的 RLock 对象,并将其用作底层锁。

在 3.3 版更改: 从工厂函数变为类。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值