python多线程编程


这里将以python3为准总结python多线程编程方面的知识,主要从线程创建和线程同步两个方面总结。

1、线程创建

python3通过threading、_thread两个标准库对线程提供支持,在python2中,线程的创建是使用thread模块,但是python3中对此不做支持,为了兼容之前的版本,python3中使用_thread模块对thread模块进行重命名。所以,python3中创建线程可以用以下两种方式:

1.1 使用_thread模块进行线程创建

这种创建线程的方式也称为“函数式”,它是利用_thread模块中的start_new_thread()函数进行创建线程的,方法如下:

_thread.start_new_thread(function,args[,kwargs])

其中:

function-是待传入的线程函数

args-传递给function函数的参数,必须是个tuple类型

kwargs-可选择的参数

例如:

import time
import _thread


#_thread

def print_time(threadName,delay):
    count=0
    while count<5:
        time.sleep(delay)
        count+=1
        print("%s: %s" %(threadName,time.ctime(time.time())))

try:
    _thread.start_new_thread(print_time,("thread-1",2))
    _thread.start_new_thread(print_time,("thread-2",4))
except:
    print("error: can not start threading")

_thread模块提供了低级别的、原始的线程和简单的锁,而threading模块提供的功能相对比较丰富,例如,threading模块提供的功能还包括:

threading.currentThread():返回当前线程。

threading.enumerate():返回一个包含正在运行的线程list,正在运行的线程包括启动后和结束前的前程,除此范围外的线程不在list范围内。

threading.activeCount():返回正在运行的线程数量,与len(threading.enumerate())结果相同。

1.2 使用threading模块进行线程创建

使用threading模块进行线程创建方法和java相同,首先创建一个线程类,当然了,该类需要继承threading.Thread类,具体操作如下示例:

import threading

class myThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    def run(self):
        print("current thread is "+threading.currentThread().getName())
        i=0
        while i<10:
            print(str(i)+" ",end=" ")
            i+=1


t1=myThread()
t2=myThread()
t1.start()
t1.join()

如果不用继承的方式创建线程,threading模块中还有一种方法和_thread模块中创建线程的方法类似,代码如下:

import threading
def thread_fun():
    i=0
    print("current thread is %s" %threading.currentThread().getName())
    while i<10:
        print(i,end=" ")
        i+=1
    

t1=threading.Thread(target=thread_fun)
t1.start()
t1.join()

上面是创建线程的方式。


2、线程同步

如果多个线程同时操作某一数据的话,就需要一些机制控制这些线程对数据的访问以保证每个线程每次访问数据的结果都是正确的,具体做法就是:凡是涉及修改数据的线程都要控制它们对数据的访问次序,使得每次修改数据时都只能有一个线程修改,并且在此期间不能有其他线程访问数据,这就是线程同步机制。线程同步有以下方法:

2.1 使用Lock

Lock是最低级的同步指令,用户可以使用Lock对象达到对临界资源的同步访问机制,Lock对象方法有:

acquire([timeout]):使线程进入同步状态,如果有其他线程已经获得锁,该线程阻塞。

release():释放锁。使用前线程必须已经获取到锁,否则出错。例如:

lock=threading.Lock()    
def thread_fun():
    lock.acquire()
    i=0
    while i<10:
        print(i,end=" ")
        i+=1
    print()
    lock.release()

t1=threading.Thread(target=thread_fun)
t2=threading.Thread(target=thread_fun)
t1.start()
t2.start()
t1.join()
t2.join()

你可以注释掉lock.acquire()和lock.release()两行试一试不同的运行结果。

2.2 使用RLock

RLock表示可重入锁,也即一个锁可以被同一个线程请求多次的同步命令,拥有RLock的线程可以再次调用acquire方法,释放锁时,调用release方法的次数要与acquire调用次数相同,例如:

import threading
rlock=threading.RLock()
def fun():
    print("%s acquring lock" %(threading.currentThread()))

    if rlock.acquire():
        print("%s got the lock. " %(threading.currentThread()))
        time.sleep(1)
        print("%s acquring lock" %(threading.currentThread()))
        if rlock.acquire():
            print("%s got the lock. " %(threading.currentThread()))

        print("%s releasing lock" %(threading.currentThread()))
        rlock.release()
        print("%s releasing lock" %(threading.currentThread()))
        rlock.release()

2.3 使用Condition

Condition(条件变量)通常与一个锁关联。如果有多个Condition需要共享一个锁,则可以将一个Lock/RLock对象传递给Condition构造方法,否则,它将自己创建一个RLock对象。Condition的构造方法是Condition([Lock/RLock]),其中Lock和RLock是可选的;其实例方法有:

acquire([timeout])/release():尝试获取锁/释放锁。

wait([timeout]):使线程进入等待状态,并释放锁。使用前线程需要已经获取到锁,否则出错。

notify():从阻塞状态的线程中挑选出一个线程,通知该线程可以运行了。使用前也需要已经获取到了锁。

notifyAll():通知所有等待的线程,这些线程都将尝试获取锁。使用前也需要已经获取到了锁。

例如,生产者和消费者可以使用条件变量进行控制:

import threading
product =None
con=threading.Condition()

def produce():
    global product

    if con.acquire():
        while True:
            if product is None:
                print("produce ...")
                product="anything"
                con.notify()
        
            con.wait()
            time.sleep(1)

def consume():
    global product
    if con.acquire():
        while True:
            if product is not None:
                print("Consuming....")
                product=None
                con.notify()

            con.wait()
            time.sleep(1)

t1=threading.Thread(target=produce)
t2=threading.Thread(target=consume)
t1.start()
t2.start()

t1.join()
t2.join()

2.4 使用Semaphore/BoundedSemaphore

操作系统中,进程间通信的一种方式就是使用信号量机制,Semaphore中有一个内置的计数器,我们可以设置其初始值表示资源个数,如果调用acquire()表示用掉一个资源,计数器减一,如果调用release()表示释放掉一个资源,计数器加一;如下示例:

import threading

semaphore=threading.Semaphore(2)

def sema_func():
    print("%s acquiring semaphore..." %(threading.currentThread().getName()))
    if semaphore.acquire():
        print("%s got semaphore" %(threading.currentThread().getName()))
        time.sleep(2)

        print("%s releasing semaphore" %(threading.currentThread().getName()))
        semaphore.release()

t1=threading.Thread(target=sema_func)
t2=threading.Thread(target=sema_func)
t3=threading.Thread(target=sema_func)
t4=threading.Thread(target=sema_func)
t1.start()
t2.start()
t3.start()
t4.start()
time.sleep(1)
#没有获取到semaphore的主线程也可以调用release
semaphore.release()

注意,如果使用BoundedSemaphore将会对信号量的释放次数进行检查,如果release()次数多于acquire()次数,将会提示出现错误。

2.5 使用Event

Event是最简单的线程通信方法:一个线程通知事件,其他线程等待事件。Event内置了一个flag,初始值为false,当调用set()方法是变为True,调用clear方法时重置为False,其实例方法有:

isSet(): 当内置标志为True时返回True。
set(): 将标志设为True,并通知所有处于等待阻塞状态的线程恢复运行状态。
clear(): 将标志设为False。
wait([timeout]): 如果标志为True将立即返回,否则阻塞线程至等待阻塞状态,等待其他线程调用set()。示例:

import threading
#event
event=threading.Event()
def event_func():
    print("%s wait for event..." %threading.currentThread().getName())
    event.wait()
    print("%s received event..." %threading.currentThread().getName())

t1=threading.Thread(target=event_func)
t2=threading.Thread(target=event_func)

t1.start()
t2.start()

time.sleep(1)
 
# 发送事件通知
print ('Main Thread set event.')
event.set()

上例中,t1,t2收到主线程的event.set()通知后,将会执行线程内部event.wait()代码后面的语句。

2.5 使用Timer

Timer继承自Thread,可以通过python编辑器命令行界面的help命令进行查看,help(threading.Timer)将会显示Timer类的内部结构。它的作用是在一定时间之后调用某个函数,其构造方法显示如下:

__init__(self, interval, function, args=None, kwargs=None)

其中,interval为指定的时间;function为要执行的方法;args(为tuple)/kwargs(为dictionary)为方法的参数。

import threading
def time_func(n,mystr):
    print(mystr)
    i=0
    while i<n:
        print(i,end=" ")
        i+=1

timer=threading.Timer(3,time_func,(5,"helloworld"))
timer.start()
timer.join()

运行以上代码,可以发现在3秒后将会执行time_func方法。

2.6 使用local

类似于java的线程本地存储,python中也有这种概念,对于一个共享资源,可以为相同变量的每个不同线程创建不同的存储,例如有5个线程使用变量x,那么线程本地存储将会生成5个用于x的不同的存储块。其大意相当于对于一个公共区的变量x,每个线程都会复制一份x作为自己的局部变量使用。这样的话,每一线程都会拥有自己独立状态的x变量。例:

import threading
local=threading.local()
local.myname="main"

def local_func():
    local.myname="local1 value"
    print(local.myname)
def local_func2():
    local.myname="local2 value"
    print(local.myname)

t1=threading.Thread(target=local_func)
t2=threading.Thread(target=local_func2)
t1.start()
t1.join()
t2.start()
t2.join()
print(local.myname)

运行上述代码,可以发现不同的线程可以为自己创建不同的local.myname的值,注意,这里的myname并不是local对象自带的,而是我们定义的变量

以上是python中创建线程和线程同步的总结,下面用lock机制完成一个多线程同步的实现,如果有一个数组,初始值全是零,有一个将该数组的所有值设置为1的线程,还有一个输出该数组值的线程,为了实现同步控制,我们可以为这两个线程设置同一把锁,谁获取该锁谁就能对数组进行操作,这样就保证了在对数组进行写操作的时候,不能对数组同时进行读操作,反之亦然,代码如下:

import threading
#同步访问同一资源
class setArray(threading.Thread):
    #这里在创建线程的时候就传入一个锁
    def __init__(self,myList,myLock):
        threading.Thread.__init__(self)
        self.myList=myList
        self.myLock=myLock
    def run(self):
        #调用执行方法
        setOne(self.myList,self.myLock)

#将数组中的数据全部设置为1
def setOne(myList,myLock):
    mylock=myLock
    resu=mylock.acquire()
    leng=len(myList)
    print(threading.currentThread().getName())
    i=0
    while i<leng:
        if i==5:
            time.sleep(1)
        
        myList[i]=1
        i+=1
    mylock.release()
#打印数组的线程
class printArray(threading.Thread):
    #同样,在创建线程的时候就传入一把锁,这里的锁应该和修改数组的锁是同一把
    def __init__(self,myList,myLock):
        threading.Thread.__init__(self)
        self.myList=myList
        self.myLock=myLock
    def run(self):
        printOne(self.myList,self.myLock)
#打印数组的方法
def printOne(myList,myLock):
    mylock=myLock
    leng=len(myList)
    i=0
    resu=mylock.acquire()
    print(threading.currentThread())
    while i<leng:
        print(myList[i],end=" ")
        i+=1
    mylock.release()

#创建一个锁
mylock=threading.Lock()
myList=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
thread1=setArray(myList,mylock)#创建写操作线程
thread2=printArray(myList,mylock)#创建读操作线程
thread1.start()
thread2.start()

thread1.join()
thread2.join()

如果修改上面的代码,将读写线程中的锁去掉,运行程序将会发现输出的数组中有0有1,这就是不同步操作造成的结果,同步机制控制了线程访问临界资源的顺序,使得它们按序访问,不能强夺。

以上是对python线程机制的总结,其中有参考自http://www.cnblogs.com/huxi/archive/2010/06/26/1765808.html的内容,未能提前告知,望博主理解

优质内容筛选与推荐>>
1、html 表格head头部不动 body部分滚动,每格宽同内容增加
2、Visual Studio 2005窗体配置文件
3、搭建CentOs7的WebServer
4、做站偏离了航道 违背了初衷
5、java的内存模型及结构


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn