写在篇前
threading模块是python多线程处理包,使用该模块可以很方便的实现多线程处理任务,本篇文章的基础是需要掌握进程、线程基本概念,对PV原语、锁等传统同步处理方法有一定的了解。另外,threading模块的实现是参考java多线程处理方式,并且只实现了其中的一个子集。必须说明的是,由于GIL的存在,多线程的应用主要用于IO密集型任务,不适合CPU密集型任务,如果要提高CPU的利用率,需要利用协程或则多进程编程。
Threading 方法属性
threading.active_count()
返回当前活动的线程数threading.current_thread()
返回当前线程的Thread对象threading.get_ident()
获取当前线程的(唯一)标识符threading.enumerate()
返回一个包含当前处于活动状态Thread 对象的listthreading.main_thread()
返回当前线程的主线程threading.settrace(func)
为每个线程设置一个trace函数,在调用run方法之前会被执行threading.setprofile(func)
同上,为每一个线程设置一个profile函数threading.stack_size([size])
设置每个线程私有栈空间大小,默认为0,若不为0不可低于32KB(32768)threading.TIMEOUT_MAX
允许线程被堵塞的最长时间,按我的理解一般用不上
Thread对象
使用Thread一共有两种方式,同Java多线程模型:
(1) 继承Thread类,并重写且仅仅重写__init__
和 run
方法,特别注意的是重写__init__
方法首先应该调用父类构造方法Thread.__init__
(2)将一个函数传入到Thread类的构造函数
Thread对象的用法以及主要方法介绍,我们用以下例子来说明一下:
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 11:26'
import threading
import time
def func1(num):
time.sleep(3)
print(str(num)+':you can if you will')
def func2(num):
time.sleep(2)
print(str(num)+':the devil is in the details')
def func3(num):
time.sleep(1)
print(str(num)+':life is short, u need what?')
def main():
start = time.time()
print('start mian Thread')
ts = (threading.Thread(group=None, target=func1, args=(1,)),
threading.Thread(group=None, target=func2, args=(2,)),
threading.Thread(group=None, target=func3, args=(3,))) # 【重要】创建三个线程,分别执行func1~3
for _t in ts:
_t.start() # 【重要方法】启动线程
# _t.join() # 【重要方法】join方法用来告诉父线程,您要等我子线程执行完毕,你再继续往下走,这里注释了,所以你应该会发现程序输出 mian Thread ends, cost 0 secs
end = time.time()
print('mian Thread ends, cost %d secs' % (end-start))
if __name__ == '__main__':
main()
# 以下是执行结果
start mian Thread
mian Thread ends, cost 0 secs
3:life is short, u need what?
2:the devil is in the details
1:you can if you will
Lock对象
因为资源总是有限的,如果多个线程对同一个对象进行操作,则有可能造成资源的争用,甚至导致死锁。引入锁,是一种便捷的解决方式。主要包括两个方法,acquire()
和release()
:
acquire(blocking=True)
当阻塞参数设置为True(默认值)时调用,然后就会进入到locked状态直到解锁,返回True。
release()
释放占有的锁,无返回值
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 13:56'
import threading
import time
# 临界资源
num = 0
lock = threading.Lock()
def num_add(t_i):
global num
time.sleep(3)
if lock.acquire(): # 这句话相当于 if lock.acquire()
num += 1
lock.release()
print('thread %d set num %d' % (t_i, num))
def num_sub(t_i):
global num
time.sleep(3)
if lock.acquire(): # 这句话相当于 if lock.acquire()
num -= 1
lock.release()
print('thread %d set num %d' % (t_i, num))
def main():
ts = []
for i in range(5):
t = threading.Thread(target=num_add, args=(i+1,))
t.start()
ts.append(t)
for i in range(5):
t = threading.Thread(target=num_sub, args=(5+i+1,))
t.start()
ts.append(t)
for t in ts:
t.join()
print('end')
if __name__ == '__main__':
main()
# 以下是结果
thread 4 set num 1
thread 3 set num 2
thread 5 set num 3
thread 1 set num 4
thread 2 set num 5
thread 9 set num 4
thread 10 set num 3
thread 7 set num 2
thread 6 set num 1
thread 8 set num 0
end
RLock对象
RLock称之为递归锁,从某种角度来讲属于一种比Lock更安全的锁,和Lock的区别在于:在同一线程内,对RLock
进行多次acquire()
操作(或则说RLock允许递归加锁),程序不会阻塞;而如果是Lock那么将会发生堵塞。RLock所拥有的方法同上,下面例子在上例基础上稍微改动:
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 13:56'
import threading
import time
# 临界资源
num = 0
lock = threading.RLock() # 【重点】这里必须是RLock,不能是Lock
def num_add(t_i):
with lock: # 这是属于上下文管理器的一个使用用法
global num
time.sleep(3)
if lock.acquire(): # 这句话相当于 if lock.acquire()
num += 1
lock.release()
print('thread %d set num %d' % (t_i, num))
def num_sub(t_i):
global num
time.sleep(3)
if lock.acquire(): # 这句话相当于 if lock.acquire()
num -= 1
lock.release()
print('thread %d set num %d' % (t_i, num))
def main():
ts = []
for i in range(5):
t = threading.Thread(target=num_add, args=(i+1,))
t.start()
ts.append(t)
for i in range(5):
t = threading.Thread(target=num_sub, args=(5+i+1,))
t.start()
ts.append(t)
for t in ts:
t.join()
print('end')
if __name__ == '__main__':
main()
Semaphore对象
信号量是计算机科学最古老的一种同步原语,由荷兰计算机科学家Edsger W. Dijkstra提出,通常也称之为PV原语。在对象内部会维护一个计数器,这个计数器的初值由Semaphore初始化时给出,这个值我们一般称之为 临界资源数量。当临界资源数量大于0时,线程不会被阻塞;当临界资源等于0时再有线程请求,那将会被阻塞,直到某个线程释放临界资源。主要方法同RLock、Lock,下面用PV原语最(也许吧)经典的例子,哲学家问题来说明一下:
哲学家问题:话说有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上,在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐;进餐完毕,放下筷子又继续思考。请设计一种方法,使哲学家不要饿死。
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 15:35'
import threading
import time
def philosopher(i, chopstick_sema, sema):
"""
描述哲学家活动
:type i: int 表示第几个哲学家
:return:no return
"""
while True:
think(i + 1)
sema.acquire(blocking=True) # 请求吃饭
chopstick_sema[i].acquire() # 拿起左边筷子
chopstick_sema[((i + 1) % 5)].acquire() # 拿起右边筷子
eat(i + 1)
chopstick_sema[((i + 1) % 5)].release() # 放下右边筷子
chopstick_sema[i].release() # 放下左边筷子
sema.release() # 吃饭完毕
think(i + 1)
def eat(phil_NO):
print('philosopher %d is eating' % phil_NO)
time.sleep(1)
print('philosopher %d finish eating' % phil_NO)
def think(phil_NO):
print('philosopher %d is thinking' % phil_NO)
time.sleep(2)
print('philosopher %d finish thinking' % phil_NO)
def main():
# 为了避免死锁,同时只允许四个哲学家吃饭
sema = threading.Semaphore(value=4)
chopstick_sema = [threading.Semaphore(value=1) for i in range(5)]
ts = []
for i in range(5):
t = threading.Thread(target=philosopher, args=(i, chopstick_sema, sema))
t.start()
ts.append(t)
for t in ts:
t.join()
if __name__ == '__main__':
main()
Condition对象
Condition对象总是与某种锁相关联,锁对象可以通过构造函数传入,或者它会默认创建一个锁,并且锁是Condition对象的一部分,不必单独跟踪它。
acquire()
尝试获取锁
release()
释放已获得的锁
wait(timeout=-1)
主动进入等待阻塞状态,直到被其他线程唤醒或则超时
wait_for(predicate, timeout=None)
进入等待状态,直到条件被置为True,timeout参数意思如上,predicate参数表示一个callable对象。
notify()
唤醒其中一个线程
notify_all()
唤醒所有进程
举个例子:
假设有一个缓冲区大小为5,要实现互斥存取数据,代码如下:
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 16:26'
import threading
import time
con = threading.Condition(threading.RLock())
num = 0
class Producer(threading.Thread):
"""
生产者类
"""
def __init__(self):
"""
构造函数
"""
threading.Thread.__init__(self)
def run(self):
"""
重写 run方法
:return:
"""
global num
with con:
while True:
num += 1
print('生产一个产品,现在有产品%d个' % num)
time.sleep(2)
if num >= 5:
print('缓冲区已满')
con.notify() # 唤醒等待池
con.wait() # 主动进入等待池,挂起
class Consumers(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global num
with con:
while True:
num -= 1
print("取出一个产品,还剩%d个" % num)
time.sleep(3)
if num <= 0:
print('缓冲区已空')
con.notify()
con.wait()
if __name__ == '__main__':
p = Producer()
c = Consumers()
p.start()
c.start()
p.join()
c.join()
Event对象
Event,翻译作事件,个人觉得该词是计算机编程中的一个专有名词。在这里用来线程之间的通信,当某一个event发生之后,其他线程做出一定的反应;或者说某一个线程等待另一个线程某个事件的发生。每一个Event对象内部维护一个flag,表征事件是否已经发生。
is_set()
当且仅当Event对象内部维护的flag为True时,返回True,否则返回False
set()
将flag置为True
clear()
将flag置为False
wait(timeout=-1)
当flag是False时,阻塞;一旦为True或超时,立刻唤醒
举个例子,好比像心意女生表白:
#! /usr/bin/python
# _*_ coding: utf-8 _*_
__author__ = 'Jeffery'
__date__ = '2018/9/15 14:58'
import threading
import time
event = threading.Event()
# 假设你想表白某位心仪女孩
# 你想,表白时送点花,更有气氛,于是乎你打电话去花店,叫老板送一束花过来
# 然后再表白
def confess_to_girl():
print('u r preparing to confess')
event.wait()
print('start to confess: hi , my loving girl, I....')
def flower_delivering():
print('flower is delivering, plea wait')
time.sleep(5)
print('flower comes')
event.set()
def main():
t1 = threading.Thread(target=confess_to_girl)
t2 = threading.Thread(target=flower_delivering)
t1.start()
t2.start()
t1.join()
t2.join()
print('\nstory,maybe new start, maybe ending!')
if __name__ == '__main__':
main()
Timer对象
计时器,即让一个线程在一个指定的之间之后再开始执行,是属于Thread的一个封装子类,故用法基本相似。
def hello():
print("hello, world")
t = Timer(30.0, hello)
# t.cancel() # 在开始之前,还可以使计时器停止
t.start() # after 30 seconds, "hello, world" will be printed
Barrier对象
Barrier,顾名思义就是障碍的意思,只有人多力量大的时候,才能一起跨过一个个障碍,继续前行。Barrier则是一个线程障碍,只有线程数量达到指定数量之后,才能唤醒所有等待进程。这个功能不是很常用,简单介绍一下吧,主要方法有:
wait(timeout=None)
使线程进入等待状态,直到达到一定数量,这时将会使barrier进入broken状态,从而一齐释放被堵塞的线程
reset()
使Barrier恢复默认‘拦截’状态
abort()
使Barrier进入Broken状态
parties
即上面提到的 指定数量的线程,int类型
n_waiting
正在等待的线程
broken
bool类型,指示Barrier是否处于Broken状态