Python多线程

来源:互联网 发布:数学竞赛知乎 编辑:程序博客网 时间:2024/06/06 10:43

1. 概念简介

回忆以下以下概念:

(1)进程

进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。进程之间只能使用进程间通讯(IPC),而不能直接共享信息。

(2)线程

线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。线程有开始顺序执行结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能。

2. Python中的线程

(1)全局解释器锁(GIL)

Python 代码的执行由 Python 虚拟机(也叫解释器主循环)来控制。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻,只有一个线程在解释器中运行。

对 Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python 虚拟机按以下方式执行

  1. 设置 GIL
  2. 切换到一个线程去运行
  3. 运行:
    a. 指定数量的字节码指令,或者
    b. 线程主动让出控制(可以调用 time.sleep(0))
  4. 把线程设置为睡眠状态
  5. 解锁 GIL
  6. 再次重复以上所有步骤

对所有面向 I/O 的(会调用内建的操作系统 C 代码的)程序来说,GIL 会在这个 I/O 调用之前被释放, 以允许其它的线程在这个线程等待 I/O 的时候运行。 如果某线程并未使用很多 I/O 操作,它会在自己的时间片内一直占用处理器(和 GIL)。也就是说,I/O 密集型的 Python 程序比计算密集型的程序更能充分利用多线程环境的好处

(2)线程退出

线程可以调用 thread.exit()之类的退出函数,也可以使用Python 退出进程的标准方法,如 sys.exit()或抛出一个 SystemExit 异常等。不过,你不可以直接“杀掉”(“kill”)一个线程。

(3)threading模块和thread模块

有关多线程编程的模块

Python 提供了几个用于多线程编程的模块,包括 thread, threadingQueue 等。threadthreading 模块允许程序员创建和管理线程。thread 模块提供了基本的线程和锁的支持,而 threading 提供了更高级别,功能更强的线程管理的功能。Queue 模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。


避免使用thread模块

  • 首先,更高级别的 threading 模块更为先进, 对线程的支持更为完善, 而且使用 thread 模块里的属性有可能会与 threading 出现冲突。
  • 其次,低级别的 thread 模块的同步原语很少(实际上只有一个),而 threading 模块则有很多。
  • 另一个不要使用 thread 原因是,它对于你的进程什么时候应该结束完全没有控制,当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作。
  • 只建议那些有经验的专家在想访问线程的底层结构的时候,才使用 thread 模块。

3. threading模块

(1)总览模块内部对象/函数

接下来,我们要介绍的是更高级别的 threading 模块,它不仅提供了 Thread 类,还提供了各种非常好用的同步机制。下表 出了 threading 模块里所有的对象

table

下表是函数

1

(2)守护进程

如果你设定一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。

  • 如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的 daemon 属性。即,在线程开始(调用 thread.start())之前, 调用 setDaemon()函数设定线程的 daemon 标志(thread.setDaemon(True)) 就表示这个线程“不重要”。
  • 如 果 你 想 要 等 待 子 线 程 完 成 再 退 出 , 那 就 什 么 都 不 用 做 , 或 者 显 式 地 调 用
    thread.setDaemon(False)以保证其 daemon 标志为 False。
  • 你可以调用 thread.isDaemon()函数来判断其 daemon 标志的值。新的子线程会继承其父线程的 daemon 标志。

(3)Thread

创建线程

  • 创建一个 Thread 的实例,传给它一个函数
  • 创建一个 Thread 的实例,传给它一个可调用的类对象
  • 从 Thread 派生出一个子类,创建一个这个子类的实例

建议使用方法3。

例子1

创建一个 Thread 的实例,传给它一个函数

# -*- coding: utf-8 -*-import threadingfrom time import sleep, ctime__author__ = 'BrownWong'def func1():    print u'func1 start!', ctime()    sleep(10)    print u'func1 end!', ctime()def func2():    print u'func2 start!', ctime()    sleep(5)    print u'func2 end!', ctime()def main():    # 创建两个线程    thread1 = threading.Thread(target=func1, args=())    thread2 = threading.Thread(target=func2, args=())    # 开启线程    thread1.start()    thread2.start()    # 所有线程结束后提示    thread1.join()    thread2.join()    print u'All thread is finished!', ctime()if __name__ == '__main__':    main()

output:

func1 start! Wed Nov 02 14:32:45 2016func2 start! Wed Nov 02 14:32:45 2016func2 end! Wed Nov 02 14:32:50 2016func1 end! Wed Nov 02 14:32:55 2016All thread is finished! Wed Nov 02 14:32:55 2016    

注释

  1. join()函数允许主线程等待子线程结束,这样你不用管理一堆锁。如果你的主线程除了等线程结束外,还有其它的事情要做(如处理或等待其它的客户请求), 那就不用调用 join(), 只有在你要等待线程结束的时候才要调用 join()


例子2

创建一个 Thread 的实例,传给它一个可调用对象

# -*- coding: utf-8 -*-import threadingfrom time import sleep, ctime__author__ = 'BrownWong'class ThreadFunc(object):    """    可调用类对象    """    def __init__(self, func, args):        self.func = func        self.args = args    def __call__(self):        apply(self.func, self.args)def func1():    print u'func1 start!', ctime()    sleep(10)    print u'func1 end!', ctime()def func2():    print u'func2 start!', ctime()    sleep(5)    print u'func2 end!', ctime()def main():    # 创建两个线程    thread1 = threading.Thread(target=ThreadFunc(func1, ()), args=())    thread2 = threading.Thread(target=ThreadFunc(func2, ()), args=())    # 开启线程    thread1.start()    thread2.start()    # 所有线程结束后提示    thread1.join()    thread2.join()    print u'All thread is finished!', ctime()if __name__ == '__main__':    main()


例子3:

从 Thread 派生出一个子类,创建一个这个子类的实例:

# -*- coding: utf-8 -*-from threading import Threadfrom time import sleep, ctime__author__ = 'BrownWong'class MyThread(Thread):    """    实例化Thread类    """    def __init__(self, func, args):        Thread.__init__(self)        self.func = func        self.args = args    def run(self):        apply(self.func, self.args)def func1():    print u'func1 start!', ctime()    sleep(10)    print u'func1 end!', ctime()def func2():    print u'func2 start!', ctime()    sleep(5)    print u'func2 end!', ctime()def main():    # 创建两个线程    thread1 = MyThread(func1, ())    thread2 = MyThread(func2, ())    # 开启线程    thread1.start()    thread2.start()    # 所有线程结束后提示    thread1.join()    thread2.join()    print u'All thread is finished!', ctime()if __name__ == '__main__':    main()

(4)结合Queue

多线程经常和queue.Queue一起使用。下面是一个使用例子:

from threading import Thread, current_thread, Lockfrom queue import Queuefrom time import sleepimport numpy as np__author__ = 'BrownWong'def single_process(in_queue):    """    :param in_queue: 输入打印队列    :return:    Description:        单个线程需要处理的工作:输出队列    """    while not exit_flag:        my_lock.acquire()        if not in_queue.empty():            print(in_queue.get(), current_thread())            my_lock.release()            sleep(0.1 * np.random.randint(1, 50))        else:            my_lock.release()exit_flag = 0my_lock = Lock()# 设置队列并入队in_queue = Queue(maxsize=8)for item in range(8):    in_queue.put(item)# 开启4个线程thread_nums = 4threads = [Thread(name='Thread %s' % (i+1), target=single_process, args=(in_queue, )) for i in range(thread_nums)]for thread in threads:    thread.start()# 等待队列清空while not in_queue.empty():    pass# 通知子线程退出exit_flag = 1# 等所有线程结束,打印结束for thread in threads:    thread.join()print('End')

Output:

0 <Thread(Thread 1, started 5184)>1 <Thread(Thread 2, started 3224)>2 <Thread(Thread 3, started 3820)>3 <Thread(Thread 4, started 4284)>4 <Thread(Thread 1, started 5184)>5 <Thread(Thread 3, started 3820)>6 <Thread(Thread 2, started 3224)>7 <Thread(Thread 4, started 4284)>End

(5)使用Event对象

This is one of the simplest mechanisms for communication between threads: one thread signals an event and other threads wait for it.An event object manages an internal flag that can be set to true with the set() method and reset to false with the clear() method. The wait()method blocks until the flag is true.

下面一个例子演示两个线程交替打印:

# -*- coding: utf-8 -*-from threading import Thread, Eventimport time__author__ = 'BrownWong'class ThreadA(Thread):    def __init__(self, event):        Thread.__init__(self)        self.event = event    def run(self):        count = 0        while count < 5:            time.sleep(1)            if self.event.is_set():                print('A')                self.event.clear()            count += 1class ThreadB(Thread):    def __init__(self, event):        Thread.__init__(self)        self.event = event    def run(self):        count = 0        while count < 5:            time.sleep(1)            if not self.event.is_set():                print('B')                self.event.set()            count += 1my_event = Event()ta = ThreadA(my_event)tb = ThreadB(my_event)ta.start()tb.start()ta.join()tb.join()

Output:

BABABABAB

(6)锁Lock

Locks are the most fundamental synchronization mechanism provided by the threading module. At any time, a lock can be held by a single thread, or by no thread at all. If a thread attempts to hold a lock that’s already held by some other thread, execution of the first thread is halted until the lock is released.

Locks are typically used to synchronize access to a shared resource. For each shared resource, create a Lock object. When you need to access the resource, call acquire to hold the lock (this will wait for the lock to be released, if necessary), and call release to release it:

lock = Lock()lock.acquire() # will block if lock is already held... access shared resourcelock.release()

你可以使用try-finally子句或者with子句:

lock.acquire()try:    ... access shared resourcefinally:    lock.release() # release lock, no matter what

或者

with lock:    ... access shared resource

(7)可重入锁RLock

The RLock class is a version of simple locking that only blocks if the lock is held by another thread. While simple locks will block if the same thread attempts to acquire the same lock twice, a re-entrant lock only blocks if another thread currently holds the lock.

# 普通锁多次获取锁会阻塞lock = threading.Lock()lock.acquire()lock.acquire()# 这样做不会阻塞lock = threading.RLock()lock.acquire()lock.acquire()

可重入锁主要使用在嵌套获取共享资源,如下例:

lock = threading.RLock()def get_first_part():    lock.acquire()    try:        ... fetch data for first part from shared object    finally:        lock.release()    return datadef get_second_part():    lock.acquire()    try:        ... fetch data for second part from shared object    finally:        lock.release()    return datadef get_both_parts():    lock.acquire()    try:        first = get_first_part()        second = get_second_part()    finally:        lock.release()    return first, second

(8)信号量

A semaphore is a more advanced lock mechanism. A semaphore has an internal counter rather than a lock flag, and it only blocks if more than a given number of threads have attempted to hold the semaphore. Depending on how the semaphore is initialized, this allows multiple threads to access the same code section simultaneously.

semaphore = threading.BoundedSemaphore()semaphore.acquire() # decrements the counter... access the shared resourcesemaphore.release() # increments the counter

Semaphores are typically used to limit access to resource with limited capacity, such as a network connection or a database server. Just initialize the counter(默认为1) to the maximum number, and the semaphore implementation will take care of the rest.

Python’s threading module provides two semaphore implementations; the Semaphore class provides an unlimited semaphore which allows you to call release any number of times to increment the counter. To avoid simple programming errors,it’s usually better to use the BoundedSemaphore class, which considers it to be an error to call release more often than you’ve called acquire.

(9)Condition对象

Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量,除了提供与Lock类似的acquirerelease方法外,还提供了waitnotify方法。

使用Condition的主要方式为:线程首先acquire一个条件变量,然后判断一些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

经典的生产者消费者问题

import threadingimport timeclass Producer(threading.Thread):    def __init__(self):        threading.Thread.__init__(self)    def run(self):        global condition, products        while True:            if condition.acquire():                if products < 10:                    products += 1                    print("Producer(%s):deliver one, now products:%s" % (self.name, products))                    condition.notify()                else:                    print("Producer(%s):already 10, stop deliver, now products:%s" % (self.name, products))                    condition.wait()                condition.release()                time.sleep(2)class Consumer(threading.Thread):    def __init__(self):        threading.Thread.__init__(self)    def run(self):        global condition, products        while True:            if condition.acquire():                if products > 1:                    products -= 1                    print("Consumer(%s):consume one, now products:%s" % (self.name, products))                    condition.notify()                else:                    print("Consumer(%s):only 1, stop consume, products:%s" % (self.name, products))                    condition.wait()                condition.release()                time.sleep(2)if __name__ == "__main__":    condition = threading.Condition()    products = 0    for p in range(0, 2):        p = Producer()        p.start()    for c in range(0, 10):        c = Consumer()        c.start()

Output:

Producer(Thread-1):deliver one, now products:1Producer(Thread-2):deliver one, now products:2Consumer(Thread-3):consume one, now products:1Consumer(Thread-4):only 1, stop consume, products:1Consumer(Thread-5):only 1, stop consume, products:1Consumer(Thread-6):only 1, stop consume, products:1Consumer(Thread-7):only 1, stop consume, products:1Consumer(Thread-8):only 1, stop consume, products:1Consumer(Thread-9):only 1, stop consume, products:1Consumer(Thread-10):only 1, stop consume, products:1Consumer(Thread-11):only 1, stop consume, products:1Consumer(Thread-12):only 1, stop consume, products:1Producer(Thread-2):deliver one, now products:2Consumer(Thread-3):consume one, now products:1Producer(Thread-1):deliver one, now products:2......

另外:Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock;除了notify方法外,Condition对象还提供了notifyAll方法,可以通知waiting池中的所有线程尝试acquire内部锁。由于上述机制,处于waiting状态的线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有线程永远处于沉默状态。

更详细的介绍见 Thread Synchronization Mechanisms in Python


Ref

《Python核心编程》
Thread Synchronization Mechanisms in Python
使用Condition实现复杂同步

0 0