Python多线程
来源:互联网 发布:数学竞赛知乎 编辑:程序博客网 时间:2024/06/06 10:43
1. 概念简介
回忆以下以下概念:
(1)进程
进程(有时被称为重量级进程)是程序的一次执行。每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。进程之间只能使用进程间通讯(IPC),而不能直接共享信息。
(2)线程
线程(有时被称为轻量级进程)跟进程有些相似,不同的是,所有的线程运行在同一个进程中,共享相同的运行环境。线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通讯。线程一般都是并发执行的,正是由于这种并行和数据共享的机制使得多个任务的合作变为可能。
2. Python中的线程
(1)全局解释器锁(GIL)
Python 代码的执行由 Python 虚拟机(也叫解释器主循环)来控制。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻,只有一个线程在解释器中运行。
对 Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,Python 虚拟机按以下方式执行:
- 设置 GIL
- 切换到一个线程去运行
- 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用 time.sleep(0)) - 把线程设置为睡眠状态
- 解锁 GIL
- 再次重复以上所有步骤
对所有面向 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
, threading
和 Queue
等。thread
和 threading
模块允许程序员创建和管理线程。thread
模块提供了基本的线程和锁的支持,而 threading
提供了更高级别,功能更强的线程管理的功能。Queue
模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
避免使用thread
模块:
- 首先,更高级别的
threading
模块更为先进, 对线程的支持更为完善, 而且使用thread
模块里的属性有可能会与threading
出现冲突。 - 其次,低级别的
thread
模块的同步原语很少(实际上只有一个),而threading
模块则有很多。 - 另一个不要使用
thread
原因是,它对于你的进程什么时候应该结束完全没有控制,当主线程结束时,所有的线程都会被强制结束掉,没有警告也不会有正常的清除工作。 - 只建议那些有经验的专家在想访问线程的底层结构的时候,才使用
thread
模块。
3. threading
模块
(1)总览模块内部对象/函数
接下来,我们要介绍的是更高级别的 threading
模块,它不仅提供了 Thread
类,还提供了各种非常好用的同步机制。下表 出了 threading 模块里所有的对象:
下表是函数:
(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
注释:
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
类似的acquire
和release
方法外,还提供了wait
和notify
方法。
使用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实现复杂同步
- Python多线程
- Python多线程
- Python多线程
- python多线程
- python 多线程
- python多线程
- python多线程
- python 多线程
- python多线程
- Python多线程
- Python 多线程
- python多线程
- python 多线程
- {python多线程}
- Python 多线程
- Python多线程
- python 多线程
- Python 多线程
- 三种最典型的大数据存储技术路线
- js中 eval的用法
- jsp下较完整的gulp实例:压缩、md5、上传cdn、路径替换
- oracle sql*plus中执行代码块
- vm58 初长成
- Python多线程
- dojo学习日记(7)——dojo主页的设计思路
- nyoj A+B Problem(V)
- HTML5新增input类型
- Android4.2 Camera子系统
- byId
- JavaWeb连接数据库MySQL
- SVN---使用SVN下载源码
- 直播技术关键实现分析