Python标准库08 多线程与同步 (threading包)
Python主要通过标准库中的threading包来实现多线程。在当今网络时代,每个服务器都会接收到大量的请求。服务器可以利用多线程的方式来处理这些请求,以提高对网络端口的读写效率。Python是一种网络服务器的后台工作语言 (比如豆瓣网),所以多线程也就很自然被Python语言支持。
(关于多线程的原理和C实现方法,请参考我之前写的Linux多线程与同步,要了解race condition, mutex和condition variable的概念)
多线程售票以及同步
我们使用Python来实现Linux多线程与同步文中的售票程序。我们使用mutex (也就是Python中的Lock类对象) 来实现线程的同步:

# A program to simulate selling tickets in multi-thread way
# Written by Vamei import threading
import time
import os # This function could be any function to do other chores.
def doChore():
time.sleep(0.5) # Function for each thread
def booth(tid):
global i
global lock
while True:
lock.acquire() # Lock; or wait if other thread is holding the lock
if i != 0:
i = i - 1 # Sell tickets
print(tid,':now left:',i) # Tickets left
doChore() # Other critical operations
else:
print("Thread_id",tid," No more tickets")
os._exit(0) # Exit the whole process immediately
lock.release() # Unblock
doChore() # Non-critical operations # Start of the main function
i = 100 # Available ticket number
lock = threading.Lock() # Lock (i.e., mutex) # Start 10 threads
for k in range(10):
new_thread = threading.Thread(target=booth,args=(k,)) # Set up thread; target: the callable (function) to be run, args: the argument for the callable
new_thread.start() # run the thread

我们使用了两个全局变量,一个是i,用以储存剩余票数;一个是lock对象,用于同步线程对i的修改。此外,在最后的for循环中,我们总共设置了10个线程。每个线程都执行booth()函数。线程在调用start()方法的时候正式启动 (实际上,计算机中最多会有11个线程,因为主程序本身也会占用一个线程)。Python使用threading.Thread对象来代表线程,用threading.Lock对象来代表一个互斥锁 (mutex)。
有两点需要注意:
- 我们在函数中使用global来声明变量为全局变量,从而让多线程共享i和lock (在C语言中,我们通过将变量放在所有函数外面来让它成为全局变量)。如果不这么声明,由于i和lock是不可变数据对象,它们将被当作一个局部变量(参看Python动态类型)。如果是可变数据对象的话,则不需要global声明。我们甚至可以将可变数据对象作为参数来传递给线程函数。这些线程将共享这些可变数据对象。
- 我们在booth中使用了两个doChore()函数。可以在未来改进程序,以便让线程除了进行i=i-1之外,做更多的操作,比如打印剩余票数,找钱,或者喝口水之类的。第一个doChore()依然在Lock内部,所以可以安全地使用共享资源 (critical operations, 比如打印剩余票数)。第二个doChore()时,Lock已经被释放,所以不能再去使用共享资源。这时候可以做一些不使用共享资源的操作 (non-critical operation, 比如找钱、喝水)。我故意让doChore()等待了0.5秒,以代表这些额外的操作可能花费的时间。你可以定义的函数来代替doChore()。
OOP创建线程
上面的Python程序非常类似于一个面向过程的C程序。我们下面介绍如何通过面向对象 (OOP, object-oriented programming,参看Python面向对象的基本概念和Python面向对象的进一步拓展) 的方法实现多线程,其核心是继承threading.Thread类。我们上面的for循环中已经利用了threading.Thread()的方法来创建一个Thread对象,并将函数booth()以及其参数传递给改对象,并调用start()方法来运行线程。OOP的话,通过修改Thread类的run()方法来定义线程所要执行的命令。

# A program to simulate selling tickets in multi-thread way
# Written by Vamei import threading
import time
import os # This function could be any function to do other chores.
def doChore():
time.sleep(0.5) # Function for each thread
class BoothThread(threading.Thread):
def __init__(self, tid, monitor):
self.tid = tid
self.monitor = monitor
threading.Thread.__init__(self)
def run(self):
while True:
monitor['lock'].acquire() # Lock; or wait if other thread is holding the lock
if monitor['tick'] != 0:
monitor['tick'] = monitor['tick'] - 1 # Sell tickets
print(self.tid,':now left:',monitor['tick']) # Tickets left
doChore() # Other critical operations
else:
print("Thread_id",self.tid," No more tickets")
os._exit(0) # Exit the whole process immediately
monitor['lock'].release() # Unblock
doChore() # Non-critical operations # Start of the main function
monitor = {'tick':100, 'lock':threading.Lock()} # Start 10 threads
for k in range(10):
new_thread = BoothThread(k, monitor)
new_thread.start()

我们自己定义了一个类BoothThread, 这个类继承自thread.Threading类。然后我们把上面的booth()所进行的操作统统放入到BoothThread类的run()方法中。注意,我们没有使用全局变量声明global,而是使用了一个词典monitor存放全局变量,然后把词典作为参数传递给线程函数。由于词典是可变数据对象,所以当它被传递给函数的时候,函数所使用的依然是同一个对象,相当于被多个线程所共享。这也是多线程乃至于多进程编程的一个技巧 (应尽量避免上面的global声明的用法,因为它并不适用于windows平台)。
上面OOP编程方法与面向过程的编程方法相比,并没有带来太大实质性的差别。
其他
threading.Thread对象: 我们已经介绍了该对象的start()和run(), 此外:
- join()方法,调用该方法的线程将等待直到改Thread对象完成,再恢复运行。这与进程间调用wait()函数相类似。
下面的对象用于处理多线程同步。对象一旦被建立,可以被多个线程共享,并根据情况阻塞某些进程。请与Linux多线程与同步中的同步工具参照阅读。
threading.Lock对象: mutex, 有acquire()和release()方法。
threading.Condition对象: condition variable,建立该对象时,会包含一个Lock对象 (因为condition variable总是和mutex一起使用)。可以对Condition对象调用acquire()和release()方法,以控制潜在的Lock对象。此外:
- wait()方法,相当于cond_wait()
- notify_all(),相当与cond_broadcast()
- nofify(),与notify_all()功能类似,但只唤醒一个等待的线程,而不是全部
- threading.Semaphore对象: semaphore,也就是计数锁(semaphore传统意义上是一种进程间同步工具,见Linux进程间通信)。创建对象的时候,可以传递一个整数作为计数上限 (sema = threading.Semaphore(5))。它与Lock类似,也有Lock的两个方法。
- threading.Event对象: 与threading.Condition相类似,相当于没有潜在的Lock保护的condition variable。对象有True和False两个状态。可以多个线程使用wait()等待,直到某个线程调用该对象的set()方法,将对象设置为True。线程可以调用对象的clear()方法来重置对象为False状态。
- 练习
- 参照Linux多线程与同步中的condition variable的例子,使用Python实现。同时考虑使用面向过程和面向对象的编程方法。
- 更多的threading的内容请参考:
http://docs.python.org/library/threading.html
总结
threading.Thread
Lock, Condition, Semaphore, Event
Python标准库08 多线程与同步 (threading包)的更多相关文章
- Python标准库12 数学与随机数 (math包,random包)
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们已经在Python运算中看到Python最基本的数学运算功能.此外,math包 ...
- 【python标准库学习】thread,threading(一)多线程的介绍和使用
在单个程序中我们经常用多线程来处理不同的工作,尤其是有的工作需要等,那么我们会新建一个线程去等然后执行某些操作,当做完事后线程退出被回收.当一个程序运行时,就会有一个进程被系统所创建,同时也会有一个线 ...
- Python标准库09 当前进程信息 (os包)
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 我们在Linux的概念与体系,多次提及进程的重要性.Python的os包中有查询和 ...
- python --标准库 路径与文件 (os.path包, glob包)
os.path包 os.path包主要是处理路径字符串,提取出有用信息. #coding:utf-8 import os.path path = 'D:\\Python7\\test\\data.tx ...
- python标准库00 学习准备
Python标准库----走马观花 python有一套很有用的标准库.标准库会随着python解释器一起安装在你的电脑上的.它是python的一个组成部分.这些标准库是python为你准备的利器,可以 ...
- Python标准库的学习准备
作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! Python标准库是Python强大的动力所在,我们已经在前文中有所介绍.由于标准 ...
- python 标准库 —— 线程与同步(threading、multiprocessing)
1. 创建线程 使用 os 下的 fork() 函数调用(仅限 Unix 系统) import os print('current process (%s) starts ...' % (os.get ...
- python第六天 函数 python标准库实例大全
今天学习第一模块的最后一课课程--函数: python的第一个函数: 1 def func1(): 2 print('第一个函数') 3 return 0 4 func1() 1 同时返回多种类型时, ...
- 转--Python标准库之一句话概括
作者原文链接 想掌握Python标准库,读它的官方文档很重要.本文并非此文档的复制版,而是对每一个库的一句话概括以及它的主要函数,由此用什么库心里就会有数了. 文本处理 string: 提供了字符集: ...
随机推荐
- GlassFish Server is a compliant implementation of the Java EE 7 platform
1.9 GlassFish Server Tools GlassFish Server is a compliant implementation of the Java EE 7 platform. ...
- dede后台栏目文章问题
栏目下的子栏目文档,并不记在主栏目文档数中,让我一直以为程序错误.
- 模仿ViewPager控件
自定义控件是开发中经常使用的技术.系统中自带的ViewPager实现的功能有时候不能满足开发的需要,如ViewPager没有滑动图片时的动画切换效果.通过对 ViewPager的模仿和部分功能的加强, ...
- python命令行添加Tab键自动补全
1.编写一个tab的自动补全脚本,名为tab.py #!/usr/bin/python # python tab complete import sys import readline import ...
- ABBYY FineReader出现错误和警告提示的解决办法
识别结果的质量取决于多种因素,这些因素包括原始文档的质量.结构布局和文档导出参数等.在使用ABBYY FineReader Pro for Mac OCR文字识别软件处理文档的各个阶段,应用程序均可能 ...
- (object sender,EventArgs e)是什么?
object sender:发出事件的对象 EventArgs e:对象中的数据
- How to send Email using C#
try { MailMessage mail = new MailMessage(); SmtpClient SmtpServer = new SmtpClient("smtp.gmail. ...
- Oracle中的不等于号
今天碰到一个Oracle不等于的问题,最后搜索了一下,发现下面资料,拿来跟大家分享一下 关于Oracle中的不等于号: 在Oracle中, <> != ~= ^= 都是不等于号的意思. ...
- 【oracle】 linux 下oracle 启动监听错误
Message 1070 not found; No message file for product=network, facility=TNSTNS-12545: Message 12545 no ...
- C++11中的Lambda表达式
原文地址:C++中的Lambda表达式 作者:果冻想 一直都在提醒自己,我是搞C++的:但是当C++11出来这么长时间了,我却没有跟着队伍走,发现很对不起自己的身份,也还好,发现自己也有段时间没有写C ...