首页 > 代码库 > 【python标准库学习】thread,threading(二)多线程同步

【python标准库学习】thread,threading(二)多线程同步

        继上一篇介绍了python的多线程和基本用法。也说到了python中多线程中的同步锁,这篇就来看看python中的多线程同步问题。

        有时候很多个线程同时对一个资源进行修改,这个时候就容易发生错误,看看这个最简单的程序:

import thread, time

count = 0

def addCount():
    global count
    for i in range(100000):
        count += 1

for i in range(10):
    thread.start_new_thread(addCount, ())

time.sleep(3)
print count

       创建了10个线程,每个线程对count每次增加1,共增加10万次,我们想象的结果应该是100万,但是事实真的如此吗,下面是我运行几次程序得到的结果:


        每次的结果都不一样,而且都小于100万,怎么会酱紫?因为我们想象的是线程一个一个的进行count += 1的操作,就是每个线程操作count结束后另外一个线程再去操作它,可是事实却并非如此,加入刚开始的时候,线程1拿到的count是0,在修改完成前另外一个线程2也拿到了这个count,此时count为0,然后线程1修改完成将count变成1,然后线程2也操作了一次再次将count变成1,这里就相当于浪费了一次操作,而在大量的线程进行大量的这种操作的时候就会有更多的浪费,所以最后的结果就编程了这个样子。

        那么要达到我们理想的状态,就只能每次让一个线程来操作count,当它操作完成后再让别的线程去操作它,那么别的线程拿到的count就是前面那个线程操作完成后的count。这个时候就需要同步锁了,多个线程只有一个同步锁,谁抢到就可以进行操作,操作完成后再释放锁让别的线程去抢。

1.thread模块

        thread模块中有一个锁类就是LockType,通过thread.allocate_lock()函数来创建这个锁对象,这个锁有3个函数:

        1).acquire()

           获取锁,有一个布尔值的参数,设置为True则会一直阻塞到获取锁成功,而且这个阻塞是不会被打断的,设置为False的话,就会立即返回,即使没获取到锁。如果获取成功则返回True,如果锁已经被别的线程获取,就返回False。

        2).release()

           释放锁。

        3).locked()

           判断锁是否被占用。

        将上面程序进行加锁操作:

import thread, time

count = 0
lock = thread.allocate_lock()

def addCount():
    global count, lock
    lock.acquire()
    for i in range(100000):
        count += 1
    lock.release()

for i in range(10):
    thread.start_new_thread(addCount, ())

time.sleep(3)
print count


2.threading模块

        threading模块中有Lock,RLock和Condition来控制同步锁的问题,有的时候很多的线程都需要针对同一个事件作出反应,当这个事件为False的时候线程就阻塞,当事件为True的时候就执行,这个时候就需要Event了。

       1).Lock和RLock

           Lock和RLock的用法跟thread模块中的LockType一样,但是他们之间的区别是RLock可以被同一线程多次获取,但是Lock不可以,如果同一线程对Lock锁连续获取两次就会出现死锁的情况,因为它第二次会阻塞无法中断,而锁又无法释放,而RLock获取几次就必须要释放几次:

lock = threading.Lock() #Lock对象  
lock.acquire()  
lock.acquire()  #产生了死琐。  
lock.release()  
lock.release()
rLock = threading.RLock()  #RLock对象
rLock.acquire()  
rLock.acquire() #在同一线程内,程序不会堵塞。  
rLock.release()  
rLock.release()

        2).Condition

           同步锁只能提供最基本的同步,加入只在发生某个事件才访问一个资源,这个时候就需要条件变量Condition了。Condition不仅提供了锁的acquire和release方法,还提供了wait,notify和notifyAll方法。当线程通过acquire获得锁后因为某些资源的却是需要等待就调用wait方法进入等待状态,同时释放锁持有的锁让别的线程能够获得并执行,当资源准备充足的时候就可以调用notify通知其他线程,处于等待状态的线程被唤醒重新去acquire锁,或者wait的线程超时,因为wait有个参数timeout。Condition内部维护着一个锁对象,可以通过参数指定为Lock或者RLock,默认为RLock,还维护着一个waiting池,进入等待状态的线程就会被这个waiting池锁记录,当有线程调用了notify后,Condition就会从这个waiting池中找一个线程唤醒,通知它去acquire锁进入就绪状态等待被cpu的调度,而notifyAll就会唤醒waiting池中所有的线程通知它们去acquire锁。

           最经典的条件变量的问题就是生产者消费者了,当资源为0时,消费者就wait等待生产者生产资源,当资源达到一定量时生产者就wait等待消费者来消费:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import threading, time


class Producer(threading.Thread):

    run(self):
        for i in range(3):  # 这里只循环了3次,下面消费者也只循环3次
            global x
            print ‘producer acquire lock‘
            con.acquire()
            print ‘producer get lock‘
            if x > 0:       # 当资源量大于零就等待消费者消费
                print ‘x > 0, producer wait‘
                con.wait()
                print ‘producer wake up‘
            else:
                for i in range(5):   # 一次生产5
                    x += 1
                    print ‘producing...‘ + str(x)
                    time.sleep(1)    # 每隔1秒生产1
                print ‘producer notify consumer‘
                con.notify()
            print ‘producer release lock‘
            con.release()
            time.sleep(1)   # 留更多的时间给消费者去acquire锁


class Consumer(threading.Thread):

    def run(self):
        for i in range(3):
            global x
            print ‘consumer acquire lock‘
            con.acquire()
            print ‘consumer get lock‘
            if x == 0:
                print ‘x = 0, consumer wait‘
                con.wait()
                print ‘consumer wake up‘
            else:
                for i in range(5):
                    x -= 1
                    print ‘consuming...‘ + str(x)
                    time.sleep(1)
                print ‘consumer notify producer‘
                con.notify()
            print ‘consumer release lock‘
            con.release()
            time.sleep(1)


con.threading.Condition()
x = 5

p = Producer()
c = Consumer()
p.start()
c.start()

        输出结果:


        3).Event

           有的时候多线程都等待某个事件的发生,当事件发生的时候,所有的线程就会被激活而执行。Event内置一个初始值为False的标志,调用set()就设置为True,调用clear()就设置为False,wait()方法则使所有线程处于等待状态,知道Event被设置为True。可以通过isSet方法查询Event对象的内置对象的当前状态。有点像车辆过红绿灯,灯变绿的时候就通行,灯变红就等待:

#! /usr/bin/env python
# -*_ coding: utf-8 -*-

import threading
import random
import time


class VehicleThread(threading.Thread):

    def __init__(self, threadName, event):
        threading.Thread.__init__(self, name=threadName)
        self.threadEvent = event

    def run(self):
        time.sleep(random.randrange(1, 10))   # 睡了随即1到10秒,让车辆陆陆续续的到达
        print ‘%s arrived at %s‘ % (self.getName(), time.ctime(time.time()))

        self.threadEvent.wait()    # 调用wait,处于等待状态直到Event为True
        print ‘%s passed through intersection at %s‘ % (self.getName(), time.ctime(time.time()))


greenLight = threading.Event()
vehicleThreads = []    # 存放所有的车辆线程

for i in range(1, 11):
    vehicleThreads.append(VehicleThread(‘Vehicle‘ + str(i), greenLight))

for vehicle in vehicleThreads:
    vehicle.start()

while threading.activeCount() > 1:    # 因为activeCount得到所有线程的数量,包括了主线程main,所以是>1
    greenLight.clear()    # 将Event标示为False,标示红灯,所有线程等待
    print ‘RED LIGHT! at:‘, time.ctime(time.time())
    time.sleep(3)

    print ‘GREEN LIGHT! at:‘, time.ctime(time.time())
    greenLight.set()    # 睡了3秒,红灯结束,将Event标示为True,所有线程被激活
    time.sleep(1)

        输出结果: