【python】python中的多线程编程方法

部分参考来源

http://blog.sina.com.cn/s/blog_5a2bbc860101gedc.html
https://www.cnblogs.com/liliuguang/p/9510940.html
https://blog.csdn.net/wh_sjc/article/details/70283843
https://juejin.im/post/5d515c7551882511ed7c273c
https://www.cnblogs.com/Allen-rg/p/7172958.html
https://www.cnblogs.com/fmqdblog/p/10680077.html
https://blog.csdn.net/jiduochou963/article/details/88020415

什么是进程

进程是资源分配的最小单元。进程是,可并发执行的程序,在某个数据集合上的一次计算活动,它是一个动态的概念。而,程序是指令的有序集合,其本身没有任何运行的含义,是一个静态的概念。程序可以作为一种软件资料长期存在,而进程是有一定生命期的程序是永久的,进程是暂时的

进程的特性:

  • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的
  • 并发性:任何进程都可以同其他进程一起并发执行
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进

进程的结构特征:进程由程序、数据和进程控制块三部分组成

另外,多个不同的进程可以包含相同的程序,一个程序在不同的数据集里就构成不同的进程,能得到不同的结果,但是执行过程中,程序不能发生改变。

并发,和,并行,的区别:

  • 并发:当有多个进程(或线程)在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的进程(或线程),它只能把CPU运行时间划分成若干个时间段再将时间段分配给各个进程(或线程)执行,在一个时间段的进程(或线程)代码运行时,其它进程(或线程)处于挂起状。这种方式称之为并发(Concurrent)
  • 并行:当系统有一个以上CPU时,则进程(或线程)的操作有可能非并发。当一个CPU执行一个进程(或线程)时,另一个CPU可以执行另一个进程(或线程),两个进程(或线程)互不抢占CPU资源,可以同时进行,这种方式称之为并行(Parallel)

进程之间的通信方式:

  • 管道:有匿名管道和命名管道两种,匿名管道只能用于具有亲缘关系的进程之间的通信,命名管道可以在无关的进程之间交换数据
  • 消息队列
  • 内存共享:指两个或多个进程共享一个给定的存储区,是最快的进程之间的通信方式。存在多进程竞争内存的问题
  • 信号量:本质就是一个计数器,用来实现进程之间的互斥与同步。信号量+内存共享,解决多进程竞争内存的问题
  • Socket

进程之间的同步方式:

  • 信号量:在信号量上只有三种操作可以进行:初始化,P操作和V操作,这三种操作都是原子操作。P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。原子操作是指不会被调度机制打断的操作,这种操作一旦开始,就一直运行到结束
  • 管程
  • 消息传递

什么是线程

线程是任务执行的最小单元一个进程可以有多个线程线程共享进程的所有资源。线程上下文切换比进程上下文切换要快得多

线程之间的通信方式:

  • 锁机制:包括互斥锁、条件变量、读写锁
    1. 互斥锁:提供了以排他方式防止数据结构被并发修改的方法
    2. 读写锁:允许多个线程同时共享数据,而对写操作是互斥的
    3. 条件变量:可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用
  • 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
  • 信号机制(Signal):类似进程间的信号处理

线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制

python中的多线程编程方法

主要转载自https://blog.csdn.net/jiduochou963/article/details/88020415,理论总结的很清晰,程序示例很好,感谢原博博主的工作。

简单介绍多线程编程

  1. 示例一
# -*- coding:utf-8 -*-

from time import sleep, ctime
                                                                                                                                                                                                            
def loop0():
    print('start loop 0 at:', ctime())
    sleep(4)
    print('loop 0 done at:', ctime())

def loop1():
    print('start loop 1 at:', ctime())
    sleep(2)
    print('loop 1 done at:', ctime())

def main():
    print('starting at:', ctime())
    loop0()
    loop1()
    print('all DONE at:', ctime())

if __name__ == '__main__':
    main()

程序输出如下:

starting at: Mon Feb 24 16:19:34 2020
start loop 0 at: Mon Feb 24 16:19:34 2020
loop 0 done at: Mon Feb 24 16:19:38 2020
start loop 1 at: Mon Feb 24 16:19:38 2020
loop 1 done at: Mon Feb 24 16:19:40 2020
all DONE at: Mon Feb 24 16:19:40 2020

可见,66当不使用多线程编程时,程序按顺序先执行函数loop0,执行完成后,再执行函数loop1。

  1. 示例二
    _thread库,锁,的使用方法:
函数/方法说明
_thread.start_new_thread(function, args, kwargs=None)派生一个新的线程,使用给定的 args 和可选的 kwargs 来执行 function
_thread.allocate_lock()分配 LockType 锁对象
_thread.exit()给线程退出指令
_thread.LockType锁对象的方法
_thread.acquire(wait=None)尝试获取锁对象
_thread.locked()如果获取了锁对象则返回 True,否则,返回 False
_thread.release()释放锁
# -*- coding:utf-8 -*-

import _thread as thread                                                                                                                                                                                    
from time import sleep, ctime

def loop0():
    print('start loop 0 at:', ctime())
    sleep(4)
    print('loop 0 done at:', ctime())

def loop1():
    print('start loop 1 at:', ctime())
    sleep(2)
    print('loop 1 done at:', ctime())

def main():
    print('starting at:', ctime())
    thread.start_new_thread(loop0, ()) 
    thread.start_new_thread(loop1, ()) 
    sleep(6)
    print('all DONE at:', ctime())

if __name__ == '__main__':
    main()

程序输出如下:

starting at: Mon Feb 24 17:02:33 2020
start loop 1 at: Mon Feb 24 17:02:33 2020
start loop 0 at: Mon Feb 24 17:02:33 2020
loop 1 done at: Mon Feb 24 17:02:35 2020
loop 0 done at: Mon Feb 24 17:02:37 2020
all DONE at: Mon Feb 24 17:02:39 2020

可见两个子线程loop0个loop1同时开始执行,这里加sleep(6)用来作为同步机制,因为,_thread 模块对于进程何时退出没有控制,当主线程结束时,所有其他线程也都强制结束

  1. 示例三
# -*- coding:utf-8 -*-

import _thread as thread                                                                                                                                                                                    
from time import sleep, ctime

loops = [4, 2]

def loop(nloop, nsec, lock):
    print('start loop', nloop, 'at:', ctime())
    sleep(nsec)
    print('loop', nloop, 'done at:', ctime())
    lock.release()

def main():
    print('starting at', ctime())
    locks = []
    nloops = range(len(loops))  

    for i in nloops:
        lock = thread.allocate_lock()  # 得到锁对象
        lock.acquire()  # 取得锁,效果等同于将锁锁上
        locks.append(lock)  # 将锁添加到锁列表中

    for i in nloops:
        thread.start_new_thread(loop, (i, loops[i], locks[i]))

    for i in nloops:
        while locks[i].locked():
            pass

    print('all DONE at:', ctime())

if __name__ == '__main__':
    main()

程序输出如下:

starting at Mon Feb 24 17:16:19 2020
start loop 0 at: Mon Feb 24 17:16:19 2020
start loop 1 at: Mon Feb 24 17:16:19 2020
loop 1 done at: Mon Feb 24 17:16:21 2020
loop 0 done at: Mon Feb 24 17:16:23 2020
all DONE at: Mon Feb 24 17:16:23 2020

可见子进程loop0和loop1同样一起开始执行,但是这种锁的使用使线程的同步更加合理

threading库

相比_thread库,threading库支持,守护线程,这一概念。

守护线程的工作方式是:守护线程一般是一个等待客户端请求服务的服务器。如果没有客户端请求,守护线程就是空闲的。如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。如果主线程准备退出时,不需要等待某些子线程完成,就可以为这些子线程设置守护线程标记。该标记值为真时,表示该线程是不重要的,或者说该线程只是用来等待客户端请求而不做任何其他事情。

要将一个线程设置为守护线程,需要在启动线程之前执行如下赋值语句:Thread.daemon = True,同样,要检查线程的守护状态,也只需要检查这个值即可。一个新的子线程会继承父线程的守护标记。整个 python 程序(主线程)将在所有非守护线程退出之后才退出,换句话说,就是没有剩下存活的非守护线程时。

threading 模块的类

描 述
Thread表示一个执行线程的对象
Lock锁原语对象(和 _thread 模块中的锁一样)
RLock可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)
Condition条件变量对象,使得一个线程等待另一个线程满足特定的“条件”,比如改变状态或某个数据值
Event条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有线程将被激活
Semaphore为线程间共享的有限资源提供了一个“计数器”,如果没有可用资源时会被阻塞
BoundedSemaphore与 Semaphore 相似,不过它不允许超过初始值
Timer与 Thread 相似,不过它要在运行前等待一段时间
Barrier创建一个“障碍”,必须达到指定数量的线程后才可以继续

Thread类

Thread类属性描述
name线程名
ident线程的标识符
daemon布尔标志,表示这个线程是否是守护线程
Thread类方法描述
_init_(group=None, tatget=None, name=None, args=(),kwargs ={}, verbose=None, daemon=None)实例化一个线程对象,需要有一个可调用的 target,以及其参数 args或 kwargs。还可以传递 name 或 group 参数,不过后者还未实现。此外 , verbose 标 志 也 是 可 接 受 的。 而 daemon 的 值 将 会 设定thread.daemon 属性/标志
start()开始执行该线程
run()定义线程功能的方法(通常在子类中被应用开发者重写)
join(timeout=None)直至启动的线程终止之前一直挂起;除非给出了 timeout(秒),否则会一直阻塞
is_alive()布尔标志,表示这个线程是否还存活

使用Thread 类,创建线程的三种方法:

  1. 创建Thread 的实例,传给它一个函数
# -*- coding:utf-8 -*-

import threading
from time import sleep, ctime

loops = [4, 2]

def loop(nloop, nsec):
    print('start loop', nloop, 'at:', ctime())
    sleep(nsec)
    print('loop', nloop, 'done at:', ctime())

def main():
    print('starting at', ctime())
    threads = []
    nloops = range(len(loops))  

    for i in nloops:
        t = threading.Thread(target=loop, args=(i, loops[i]))  # args是调用target的参数
        threads.append(t)

    for i in nloops:
        threads[i].start()

    for i in nloops:
        threads[i].join()

    print('all DONE at:', ctime())

if __name__ == '__main__':
    main()

程序输出如下:

starting at Mon Feb 24 19:41:21 2020
start loop 0 at: Mon Feb 24 19:41:21 2020
start loop 1 at: Mon Feb 24 19:41:21 2020
loop 1 done at: Mon Feb 24 19:41:23 2020
loop 0 done at: Mon Feb 24 19:41:25 2020
all DONE at: Mon Feb 24 19:41:25 2020

实例化 Thread和调用 _thread.start_new_thread()的最大区别是,新线程不会立即开始执行。这是一个非常有用的同步功能,尤其是当你并不希望线程立即开始执行时。

当所有线程都分配完成之后,通过调用每个线程的 start()方法让它们开始执行,而不是在这之前就会执行。相比于管理一组锁(分配、获取、释放、检查锁状态等)而言,这里只需要为每个线程调用 join()方法即可

join()方法将等待线程结束,或者在提供了超时时间的情况下,达到超时时间。使用 join()方法要比等待锁释放的无限循环更加清晰(这也是这种锁又称为自旋锁的原因)。

对于 join()方法而言,其另一个重要方面是其实它根本不需要调用。一旦线程启动,它们就会一直执行,直到给定的函数完成后退出。如果主线程还有其他事情要去做,而不是等待这些线程完成(例如其他处理或者等待新的客户端请求),就可以不调用 join()。 join()方法只有在你需要等待线程完成的时候才是有用的

  1. 创建Thread 的实例,传给它一个可调用的类实例
# -*- coding:utf-8 -*-

import threading
from time import sleep, ctime

loops = [4, 2]

class ThreadFunc(object):
    def __init__(self, func, args):
        self.func = func
        self.args = args
        
    def __call__(self):
        self.func(*self.args)  # 拆包

def loop(nloop, nsec):
    print('start loop', nloop, 'at:', ctime())
    sleep(nsec)
    print('loop', nloop, 'done at:', ctime())

def main():
    print('starting at', ctime())
    threads = []
    nloops = range(len(loops))  

    for i in nloops:  
        t = threading.Thread(target=ThreadFunc(loop, (i, loops[i])))
        threads.append(t)

    for i in nloops: 
        threads[i].start()

    for i in nloops: 
        threads[i].join()

    print('all DONE at:', ctime())


if __name__ == '__main__':
    main()

程序输出如下:

starting at Mon Feb 24 21:18:50 2020
start loop 0 at: Mon Feb 24 21:18:50 2020
start loop 1 at: Mon Feb 24 21:18:50 2020
loop 1 done at: Mon Feb 24 21:18:52 2020
loop 0 done at: Mon Feb 24 21:18:54 2020
all DONE at: Mon Feb 24 21:18:54 2020

当创建新线程时,Thread 类的代码将调用ThreadFunc对象,此时会调用__call__()这个特殊方法。由于我们已经有了要用到的参数,这里就不需要再将其传递给Thread()的构造函数了,直接调用即可。

  1. 派生Thread 的子类,并创建子类的实例
# -*- coding:utf-8 -*-

import threading
from time import sleep, ctime

loops = [4, 2]

class MyThread(threading.Thread):
    def __init__(self, func, args):
        threading.Thread.__init__(self)
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)

def loop(nloop, nsec):
    print('start loop', nloop, 'at:', ctime())
    sleep(nsec)
    print('loop', nloop, 'done at:', ctime())

def main():
    print('starting at', ctime())
    threads = []
    nloops = range(len(loops))

    for i in nloops: 
        t = MyThread(loop, (i, loops[i]))

        threads.append(t)

    for i in nloops:  
        threads[i].start()

    for i in nloops:  
        threads[i].join()

    print('all DONE at:', ctime())

if __name__ == '__main__':
    main()

程序输出如下:

starting at Tue Feb 25 11:03:04 2020
start loop 0 at: Tue Feb 25 11:03:04 2020
start loop 1 at: Tue Feb 25 11:03:04 2020
loop 1 done at: Tue Feb 25 11:03:06 2020
loop 0 done at: Tue Feb 25 11:03:08 2020
all DONE at: Tue Feb 25 11:03:08 2020

Lock类

当多线程中需要“独占资源”的时候,要使用锁来控制,防止多个线程同时占用资源而出现其他异常

当多线程争夺锁时,允许第一个获得锁的线程进入临界区,并执行代码。所有之后到达的线程将被阻塞,直到第一个线程执行结束,退出临界区,并释放锁。此时,其他等待的线程可以获得锁并进入临界区。不过请记住,那些被阻塞的线程是没有顺序的(即不是先到先执行),胜出线程的选择是不确定的,而且还会根据 python 实现的不同而有所区别。

类方法描述
acquire()获得锁
release()释放锁
# -*- coding:utf-8 -*-
                                                                                                                                                                        
import threading
from time import sleep, ctime
 
lock = threading.Lock()
l = []
 
def test(n):
    print("id:", n, " Start time:", ctime())
    lock.acquire()
    l.append(n)
    print(l)
    sleep(2)
    print("id:", n, " End time:", ctime())
    lock.release()
 
def main():
    for i in range(0, 10):
        th = threading.Thread(target=test, args=(i, ))
        th.start()
    
if __name__ == '__main__':
    main()

程序输出如下:

id: 0 Start time: Tue Feb 25 11:55:31 2020
[0]
id: 1 Start time: Tue Feb 25 11:55:31 2020
id: 2 Start time: Tue Feb 25 11:55:31 2020
id: 3 Start time: Tue Feb 25 11:55:31 2020
id: 4 Start time: Tue Feb 25 11:55:31 2020
id: 5 Start time: Tue Feb 25 11:55:31 2020
id: 6 Start time: Tue Feb 25 11:55:31 2020
id: 7 Start time: Tue Feb 25 11:55:31 2020
id: 8 Start time: Tue Feb 25 11:55:31 2020
id: 9 Start time: Tue Feb 25 11:55:31 2020
id: 0 End time: Tue Feb 25 11:55:33 2020
[0, 1]
id: 1 End time: Tue Feb 25 11:55:35 2020
[0, 1, 2]
id: 2 End time: Tue Feb 25 11:55:37 2020
[0, 1, 2, 3]
id: 3 End time: Tue Feb 25 11:55:39 2020
[0, 1, 2, 3, 4]
id: 4 End time: Tue Feb 25 11:55:41 2020
[0, 1, 2, 3, 4, 5]
id: 5 End time: Tue Feb 25 11:55:43 2020
[0, 1, 2, 3, 4, 5, 6]
id: 6 End time: Tue Feb 25 11:55:45 2020
[0, 1, 2, 3, 4, 5, 6, 7]
id: 7 End time: Tue Feb 25 11:55:47 2020
[0, 1, 2, 3, 4, 5, 6, 7, 8]
id: 8 End time: Tue Feb 25 11:55:49 2020
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
id: 9 End time: Tue Feb 25 11:55:51 2020

可见,加了锁之后,当多个线程都要往列表中写入数据的时候,只有当一个线程写入完成后,再执行下一个线程。

信号量示例

对于拥有有限资源的应用来说,使用信号量实现线程同步是个不错的决定。
信号量是最古老的同步原语之一。它是一个计数器,当资源消耗时递减,当资源释放时递增你可以认为信号量代表它们的资源可用或不可用消耗资源使计数器递减的操作习惯上称为 P,也称为 wait、try、acquire、pend 或 procure。相对地,当一个线程对一个资源完成操作时,该资源需要返回资源池中。这个操作一般称为 V,也称为 signal、 increment、 release、 post、 vacate。

python 简化了所有的命名,使用和锁的函数/方法一样的名字: acquire 和 release。信号量比锁更加灵活,因为可以有多个线程,每个线程拥有有限资源的一个实例。

BoundedSemaphore类
类方法描述
acquire信号量P操作,计数器减一 ,但如果低于零,会一直阻塞。所以通过传入非阻塞的标志 False(acquire(False)),让调用不再阻塞,而在应当阻塞的时候返回一个False,指明没有更多的资源了
release信号量V操作,计数器加一,但如果超过初始值,报错ValueError

在下面的例子中,我们将模拟一个简化的糖果机。这个特制的机器只有 5 个可用的槽来保持库存(糖果)。如果所有的槽都满了,糖果就不能再加到这个机器中了;相似地,如果每个槽都空了,想要购买的消费者就无法买到糖果了。我们可以使用信号量来跟踪这些有限的资源(糖果槽)

# -*- coding:utf-8 -*-

from atexit import register
from random import randrange
from threading import BoundedSemaphore, Lock, Thread
from time import sleep, ctime


lock = Lock()
MAX = 5
candytray = BoundedSemaphore(MAX)


def refill():
    lock.acquire()
    print("Refilling candy...", end="")
    try:
        candytray.release()
    except ValueError:
        print("full, skipping")
    else:
        print("OK")
    lock.release()


def buy():
    lock.acquire()
    print("Buying candy...", end="")
    if candytray.acquire(False):
        print("OK")
    else:
        print("empty, skipping")
    lock.release()


def producer(loops):
    for i in range(loops):
        refill()
        sleep(randrange(3))


def consumer(loops):
    for i in range(loops):
        buy()
        sleep(randrange(3))


def main():
    print("Starting at:", ctime())
    nloops = randrange(2, 6)
    print("The Candy Machine (full with C%d bars)!" % MAX)
    Thread(target=consumer, args=(randrange(nloops, nloops + MAX + 2), )).start()
    Thread(target=producer, args=(nloops, )).start()


@register
def atexit():
    print("all DONE at:", ctime())


if __name__ == "__main__":
    main()

程序输出如下:

Starting at: Tue Feb 25 15:52:09 2020
The Candy Machine (full with C5 bars)!
Buying candy…OK
Refilling candy…OK
Buying candy…OK
Buying candy…OK
Refilling candy…OK
Refilling candy…OK
Refilling candy…full, skipping
Buying candy…OK
Buying candy…OK
all DONE at: Tue Feb 25 15:52:15 2020

生产者消费者问题

我们使用 queue 模块来提供线程间通信的机制,从而让线程之间可以互相分享数据。具体而言,就是创建一个队列让生产者(线程)在其中放入新的商品而消费者(线程)消费这些商品

queue模块

queue模块是python一个独立的第三方库,而不是threading的一个类

模块的类描述
Queue(maxsize=0)创建一个先入先出队列。如果给定最大值,则在队列没有空间时阻塞;否则(没有指定最大值),为无限队列
LifoQueue(maxsize=0)创建一个后入先出队列。如果给定最大值,则在队列没有空间时阻塞;否则(没有指定最大值),为无限队列
PriorityQueue(maxsize=0)创建一个优先级队列。如果给定最大值,则在队列没有空间时阻塞;否则(没有指定最大值),为无限队列
类方法描述
qsize()返回队列大小,注意是队列大小而不是队列容量
empty()如果队列为空,则返回 True;否则,返回 False
full()如果队列已满,则返回 True;否则,返回 False
put (item, block=Ture, timeout=None)将 item 放入队列。如果 block 为 True(默认)且 timeout 为 None,则在有可用空间之前阻塞;如果 timeout 为正值,则最多阻塞 timeout 秒;如果 block 为 False,则抛出 Empty 异常
put_nowait(item)和 put(item, False)相同
get (block=True, timeout=None)从队列中取得元素。如果给定了 block(非 0),则一直阻塞到有可用的元素为止
get_nowait()和 get(False)相同
task_done()于表示队列中的某个元素已执行完成,该方法会被下面的 join()使用
join()在队列中所有元素执行完毕并调用上面的 task_done()信号之前,保持阻塞
异常描述
Empty当对空队列调用 get*()方法时抛出异常
Full当对已满的队列调用 put*()方法时抛出异常
# -*- coding:utf-8 -*-

import threading
from time import sleep
from random import randint
from queue import Queue

lock = threading.Lock()


class MyThread(threading.Thread):
    def __init__(self, func, args):
        threading.Thread.__init__(self)
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)


def writeQ(queue):
    lock.acquire()
    print("producing object for Q...", end="")
    queue.put("xxx", 1)
    print("size now", queue.qsize())
    lock.release()


def readQ(queue):
    lock.acquire()
    val = queue.get(1)
    print("consumed object from Q... size now", queue.qsize())
    lock.release()


def writer(queue, loops):
    for i in range(loops):
        writeQ(queue)
        sleep(randint(1, 3))


def reader(queue, loops):
    for i in range(loops):
        readQ(queue)
        sleep(randint(2, 5))


funcs = [writer, reader]
nfuncs = range(len(funcs))


def main():
    nloops = randint(2, 5)
    q = Queue(32)

    threads = []
    for i in nfuncs:
        t = MyThread(funcs[i], (q, nloops))
        threads.append(t)

    for i in nfuncs:
        threads[i].start()

    for i in nfuncs:
        threads[i].join()

    print("all DONE")


if __name__ == "__main__":
    main()

程序输出如下:

producing object for Q…size now 1
consumed object from Q… size now 0
producing object for Q…size now 1
producing object for Q…size now 2
consumed object from Q… size now 1
producing object for Q…size now 2
consumed object from Q… size now 1
consumed object from Q… size now 0
all DONE

条件变量示例

Condition(条件变量)通常与一个锁关联。可以认为,除了Lock带有的锁定池外,Condition还包含一个等待池,池中的线程处于等待阻塞状态,直到另一个线程调用notify()或notifyAll()通知;得到通知后线程进入锁定池等待锁定。

Condition类
类方法描述
acquire()线程锁
release()释放锁
wait(timeout)线程挂起,直到收到一个notify通知或者超时(可选的,浮点数,单位是秒s)才会被唤醒继续运行。wait()必须在已获得Lock前提下才能调用,否则会触发RuntimeError
notify(n=1)通知其他线程,那些挂起的线程接到这个通知之后会开始运行,默认是通知一个正等待该condition的线程,最多则唤醒n个等待的线程。notify()必须在已获得Lock前提下才能调用,否则会触发RuntimeError。notify()不会主动释放Lock
notifyAll()如果wait状态线程比较多,notifyAll的作用就是通知所有线程
# -*- coding:utf-8 -*-

import threading
import random
from queue import Queue

con = threading.Condition()
q = Queue(1)


def thread_0():
    con.acquire()
    for i in range(10):
        if q.full:
            val = q.get()
            print("Get thread_0 id:", i, "value:", val)
            con.notify()
            con.wait(1)
    con.release()


def thread_1():
    con.acquire()
    for j in range(10):
        a = random.randint(0, 9)
        if q.empty():
            q.put(a)
            print("Put thread_1 id:", j, "value:", a)
            con.notify()
            con.wait(1)
    con.release()


if __name__ == "__main__":
    thread = []
    thread_name = [thread_1, thread_0]
    for each in thread_name:
        x = threading.Thread(target=each)
        thread.append(x)

    for each in range(len(thread)):
        thread[each].start()

    for each in range(len(thread)):
        thread[each].join()

程序的输出如下:

Put thread_1 id: 0 value: 4
Get thread_0 id: 0 value: 4
Put thread_1 id: 1 value: 3
Get thread_0 id: 1 value: 3
Put thread_1 id: 2 value: 1
Get thread_0 id: 2 value: 1
Put thread_1 id: 3 value: 4
Get thread_0 id: 3 value: 4
Put thread_1 id: 4 value: 2
Get thread_0 id: 4 value: 2
Put thread_1 id: 5 value: 2
Get thread_0 id: 5 value: 2
Put thread_1 id: 6 value: 3
Get thread_0 id: 6 value: 3
Put thread_1 id: 7 value: 8
Get thread_0 id: 7 value: 8
Put thread_1 id: 8 value: 8
Get thread_0 id: 8 value: 8
Put thread_1 id: 9 value: 7
Get thread_0 id: 9 value: 7

结语

如果您有修改意见或问题,欢迎留言或者通过邮箱和我联系。
手打很辛苦,如果我的文章对您有帮助,转载请注明出处。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值