Python标准库08 多线程与同步 (threading包)

原文:Python标准库08 多线程与同步 (threading包)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明。谢谢!

 

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 lockwhile 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

时间: 2024-09-11 12:37:47

Python标准库08 多线程与同步 (threading包)的相关文章

Python标准库12 数学与随机数 (math包,random包)

原文:Python标准库12 数学与随机数 (math包,random包) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   我们已经在Python运算中看到Python最基本的数学运算功能.此外,math包补充了更多的函数.当然,如果想要更加高级的数学功能,可以考虑选择标准库之外的numpy和scipy项目,它们不但支持数组和矩阵运算,还有丰富的数学和物理方程可供使用. 此外,random包可以用来生成随机数.随机数不仅可

Python标准库09 当前进程信息 (os包)

作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   我们在Linux的概念与体系,多次提及进程的重要性.Python的os包中有查询和修改进程信息的函数.学习Python的这些工具也有助于理解Linux体系.   进程信息 os包中相关函数如下: uname() 返回操作系统相关信息.类似于Linux上的uname命令. umask() 设置该进程创建文件时的权限mask.类似于Linux上的umask命令,见Linux文件管

Python标准库的学习准备

原文:Python标准库的学习准备 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   Python标准库是Python强大的动力所在,我们已经在前文中有所介绍.由于标准库所涉及的应用很广,所以需要学习一定的背景知识.   硬件原理 这一部份需要了解内存,CPU,磁盘存储以及IO的功能和性能,了解计算机工作的流程,了解指令的概念.这些内容基础而重要. Python标准库的一部份是为了提高系统的性能(比如mmap),所以有必要了

Python标准库10 多进程初步 (multiprocessing包)

原文:Python标准库10 多进程初步 (multiprocessing包) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   我们已经见过了使用subprocess包来创建子进程,但这个包有两个很大的局限性:1) 我们总是让subprocess运行外部的程序,而不是运行一个Python脚本内部编写的函数.2) 进程间只通过管道进行文本交流.以上限制了我们将subprocess包应用到更广泛的多进程任务.(这样的比较实际是

Python标准库——走马观花

原文:Python标准库--走马观花 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   Python的一大好处在于它有一套很有用的标准库(standard library).标准库是随着Python一起安装在你的电脑中的,是Python的一部分 (当然也有特殊情况.有些场合会因为系统安全性的要求,不使用全部的标准库,比如说Google App Engine).   利用已有的类(class)和函数(function)进行开发

Python标准库11 多进程探索 (multiprocessing包)

原文:Python标准库11 多进程探索 (multiprocessing包) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢!   在初步了解Python多进程之后,我们可以继续探索multiprocessing包中更加高级的工具.这些工具可以让我们更加便利地实现多进程.   进程池 进程池 (Process Pool)可以创建多个进程.这些进程就像是随时待命的士兵,准备执行任务(程序).一个进程池中可以容纳多个待命的士兵.

Python标准库之Sys模块使用详解

  这篇文章主要介绍了Python标准库之Sys模块使用详解,本文讲解了使用sys模块获得脚本的参数.处理模块.使用sys模块操作模块搜索路径.使用sys模块查找内建模块.使用sys模块查找已导入的模块等使用案例,需要的朋友可以参考下 sys 模块提供了许多函数和变量来处理 Python 运行时环境的不同部分. 处理命令行参数 在解释器启动后, argv 列表包含了传递给脚本的所有参数, 列表的第一个元素为脚本自身的名称. 使用sys模块获得脚本的参数 代码如下: print "script n

Python标准库defaultdict模块使用示例

  Python标准库中collections对集合类型的数据结构进行了很多拓展操作,这些操作在我们使用集合的时候会带来很多的便利,多看看很有好处. defaultdict是其中一个方法,就是给字典value元素添加默认类型,之前看到过但是没注意怎么使用,今天特地瞅了瞅. 首先是各大文章介绍的第一个例子: 代码如下: import collections as coll def default_factory(): return 'default value' d = coll.defaultd

Python标准库urllib2的一些使用细节总结_python

Python 标准库中有很多实用的工具类,但是在具体使用时,标准库文档上对使用细节描述的并不清楚,比如 urllib2 这个 HTTP 客户端库.这里总结了一些 urllib2 的使用细节. 1.Proxy 的设置 2.Timeout 设置 3.在 HTTP Request 中加入特定的 Header 4.Redirect 5.Cookie 6.使用 HTTP 的 PUT 和 DELETE 方法 7.得到 HTTP 的返回码 8.Debug Log Proxy 的设置 urllib2 默认会使用