0x01 线程锁

既然说到多线程,就会经常遇到多个线程共同操作一个单元或空间的情况,对该单元进行修改、获取等操作,为了避免各线程间的混乱操作,保证线程同步的正确性,很有必要引入线程锁的概念。

线程锁,顾名思义,表示某一单元或空间只为某一线程所有,此时其他线程均无法进行操作,等待该线程操作完成并释放锁之后,其他线程才可继续利用该单元。

Python中线程锁的用法很简单,使用Thread对象中的Lock方法即可,在需要加锁的地方调用accquire方法,之后调用release方法释放锁。

import threading
th_lock = threading.Lock()
th_lock.accquire()
#加锁内容
...
#th_lock.release()

用法非常简单,但在真实开发中,锁使用的位置却非常关键,它需要你考虑到你所操作单元的当前状态是否可能被其他线程修改获取等,又要考虑工作效率的最大化。

0x02 多线程与队列

我们经常会遇到这样的一个问题,这里有成千上万条数据,每次需要取出其中的一条数据进行处理,那么引入多线程该怎么进行任务分配?

我们可以将数据进行分割然后交给多个线程去跑,可是这并不是一个明智的做法。在这里我们可以使用队列与线程相结合的方式进行任务分配。

队列线程的思想: 首先创建一个全局共享的队列,队列中只存在有限个元素,并将所有的数据逐条加入到队列中,并调用队列的join函数进行等待。之后便可以开启若干线程,线程的任务就是不断的从队列中取数据进行处理就可以了。

#!/usr/bin/python
#coding=utf-8

import threading
import time
import Queue

q = Queue.Queue(10)

threadLock = threading.Lock()
class myThread(threading.Thread):
    def __init__(self,threadID,name):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.exitFlag = 0
    def run(self):
        while not self.exitFlag:
            threadLock.acquire()
            if not q.empty():
                id = q.get()
                print_time(self.name,id)
                threadLock.release()
            else:
                threadLock.release()

def print_time(threadName,id):
    pass
        # print "%s:%s:%s"%(threadName,time.ctime(time.time()),id)

# 创建3个线程
threads = []
for i in xrange(3):
    name =  "Thread-%d"%i
    t = myThread(i,name)
    t.start()
    threads.append(t)


for i in xrange(10000):
    q_name = "Queue:%d"%i
    q.put(q_name)


# 等待队列清空
while not q.empty():
    pass

# 也可以join方法,与上同效
# q.join()

# 通知线程,处理完之后关闭
for t in threads:
    t.exitFlag = 1

# 等待所有线程结束之后才退出
for t in threads:
    t.join()

print "Exiting Main Thread"

这里注意两点,首先

while not q.empty():
    pass
q.join()

两者效果相同。

其次

    threadLock.acquire()
    if not q.empty():

这里必须要在判断q.empty()前加上线程锁,因为可能会出现这样的一种情况。

某一时刻,队列中还有一个元素,该元素正在被线程A取出,而与此同时线程B正在判断队列q是否为空,而此时线程B中队列q不为空进入后面的操作,但是待B去取元素时,最后一个元素已经被A取出,造成线程等待,显示出被挂起的状态。

我们也可以通过加入q.get(timeout=10)超时操作来弥补这一问题。

0x03 后记

由于Python中的GIL全局锁的原因,多线程效果并不怎么好,并发处理网络或读写文件效果还不错,所以GG。