threading模块:多线程操作

相关概念:

  • 一个进程内,可以有多个线程。
  • 线程内分为主线程和多个子线程。
  • 多个子线程之间可以共享内存和数据。
  • 由于GIL(全局解释锁)的限制,threading只适合在I/O密集型程序上使用。

threading中定义的方法

threading.active_count()
返回Thread当前活动的线程数量。
threading.current_thread()
返回当前的线程。
threading.get_ident()
返回当前线程的“标识符”,该“标识符”是一个非零整数。
threading.enumerate()
返回一个列表,列表中包括有:当前活动的所有线程以及主线程。
threading.main_thread()
返回主线程对象,通常情况下,主线程就是启动Python解释器的线程。

threading中定义的类

threading.Thread(group = None, target = None, name = None, args = (), kwargs = {})
group:是为将来扩展ThreadGroup类是保留的,不需要管它。
target:一个可调用对象,也就是设置为子线程的函数。
name:为线程设置一个名称,默认为“Thread-N”的形式,N为一个整数。
args:被target调用的对象的参数,应该传入一个元组。
kwaegs:被target调用的对象的关键字参数,应该传入一个字典。
threading.Thread()类中的方法分别有
start():多线程必须显式地调用start()方法才能开始线程的活动。而且每个线程对象最多只能调用一次。
run():一般用于在子类中重写该方法,run()方法可以理解为threading.Thread()中的target参数。
join(timeout = None):阻塞主线程,等待子线程终止。
setDarmon(True):将子线程设置为守护线程(后台程序),设置为守护线程之后,主线程不会等待子线程完全执行完成,主线程执行结束就会直接退出Python程序。
isDaemon():判断子线程是否为守护线程。
is_alive():判断线程是否存活。
setName():给子线程设置一个名字。
getName():返回子线程的名字。

示例1:

import threading
import time

def cooking(n):
	print('I am cooking')
	time.sleep(n)
	print('cooking ok')

def listening(n):
	print('I am listening music')
	time.sleep(n)
	print('listening ok')

t1 = threading.Thread(target = cooking, args = (5,))
t2 = threading.Thread(target = listening, args = (3,))

print('Now')
t1.start()
t2.start()
print('!!!!!!')

输出如下:

Now
I am cooking
I am listening music
!!!!!!
listening ok
cooking ok
[Finished in 5.4s]

可以看出,主线程会按顺序执行,并且会等待子线程执行结束之后,才会退出程序。

示例2:

import threading
import time

def cooking(n):
	print('I am cooking')
	time.sleep(n)
	print('cooking ok')

def listening(n):
	print('I am listening music')
	time.sleep(n)
	print('listening ok')

t1 = threading.Thread(target = cooking, args = (5,))
t2 = threading.Thread(target = listening, args = (3,))

print('Now')
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
print('!!!!!!')

输出如下:

Now
I am cooking
I am listening music
!!!!!!
[Finished in 0.2s]

当把子线程设置为守护线程(设置为后台程序)的时候,主线程不会等待子线程执行完成,主线程的代码执行完成之后,就会直接退出程序。

示例3:

import threading
import time

def cooking(n):
	print('I am cooking')
	time.sleep(n)
	print('cooking ok')

def listening(n):
	print('I am listening music')
	time.sleep(n)
	print('listening ok')

t1 = threading.Thread(target = cooking, args = (5,))
t2 = threading.Thread(target = listening, args = (3,))

print('Now')
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
t1.join()
t2.join()
print('!!!!!!')

输出如下:

Now
I am cooking
I am listening music
listening ok
cooking ok
!!!!!!
[Finished in 5.2s]

这次为两个子线程都添加了join()方法,join()方法会阻塞主线程(’!!!!!!'在最后一行输出),即使两个子线程被设置为守护线程,也会等待守护线程(后台程序)执行结束之后,才会继续往下执行主线程。
可以为join()方法添加一个超时时间,如timeout = 4,则’cooking ok‘这一行,不会被输出。
示例4:

import threading
import time

class Cooking(threading.Thread):

	def __init__(self, n):
		super(Cooking, self).__init__()
		self.n = n

	def run(self):
		print('I am cooking')
		time.sleep(self.n)
		print('cooking ok')

class Listening(threading.Thread):
	
	def __init__(self, n):
		super(Listening, self).__init__()
		self.n = n 
	
	def run(self):
		print('I am listening music')
		time.sleep(self.n)
		print('listening ok')

if __name__ == '__main__':
	print('Now')
	t1 = Cooking(5)
	t2 = Listening(3)
	t1.start()
	t2.start()
	print('!!!!!!')

另一种创建子线程的方式是通过继承threading.Thread,调用超类的构造方法,并且重写run()方法。

threading.Lock()

锁不属于特定的线程,锁只有“锁定"和”解锁“两种状态。它是在”解锁“的状态下创建的。
threading.Lock()的两个基本方法分别为:
acquire():尝试获取锁
release():释放锁

示例1:未加锁的情况

import threading
import time

def cooking(n):
	print('My name is Alfred,')
	time.sleep(1)
	print('I am cooking')
	time.sleep(n)
	print('cooking ok')

def listening(n):
	print('My name is Rookie,')
	time.sleep(1)
	print('I am listening music')
	time.sleep(n)
	print('listening ok')

t1 = threading.Thread(target = cooking, args = (5,))
t2 = threading.Thread(target = listening, args = (3,))

print('Now')
t1.start()
t2.start()
t1.join()
t2.join()
print('!!!!!!')

输出如下:

Now
My name is Alfred,
My name is Rookie,
I am listening music
I am cooking
listening ok
cooking ok
!!!!!!
[Finished in 6.2s]

假如我想输出的顺序为,先介绍一个人名“My name is Alfred.",然后说明在干什么"I am cooking’,再然后介绍下一个人名。
我具体想要达到的输出顺序,如下:

Now
My name is Alfred,
I am cooking
My name is Rookie,
I am listening music
listening ok
cooking ok
!!!!!!

示例2:加锁的情况

import threading
import time

lock = threading.Lock()

def cooking(n):
	lock.acquire()
	print('My name is Alfred,')
	time.sleep(1)
	print('I am cooking')
	lock.release()
	time.sleep(n)
	print('cooking ok')

def listening(n):
	lock.acquire()
	print('My name is Rookie,')
	time.sleep(1)
	print('I am listening music')
	lock.release()
	time.sleep(n)
	print('listening ok')

t1 = threading.Thread(target = cooking, args = (5,))
t2 = threading.Thread(target = listening, args = (3,))

print('Now')
t1.start()
t2.start()
t1.join()
t2.join()
print('!!!!!!')
threading.RLock()

可重入锁,同一个线程可以多次获取该锁,然后依次释放。有“递归”的概念。
示例1:

import threading
lock = threading.Lock()
rlock = threading.RLock()

## 使用threading.Lock()的情况,用于和RLock()对比
def test1():
	print('I am lock')
	lock.acquire()
	print('I get Lock')
	lock.acquire()
	print('I get Lock 2')
	lock.release()
	lock.release()

def test2():
	print('I am rlock')
	rlock.acquire()
	print('I get rLock')
	rlock.acquire()
	print('I get rLock 2')
	rlock.release()
	rlock.release()
	

执行test1()的时候,会出现死锁,程序只输出前两句print,而且程序永远不会停止。
执行test2()的时候,程序的3个print语句正常执行。

threading.Condition()

threading.Condition()默认自带一个RLock,另外还带有一个“等待池”,等待池中的线程处于阻塞状态。
threading.Condition()类带有的方法有:
acquire():获取Condition()自带的锁
release():释放锁
wait():该方法会释放锁,然后阻塞线程。
notify():该方法会唤醒“等待池”中线程。
notify_all():同notify(),但notify()只唤醒其中一个线程,而notify_all()唤醒所有线程。
注意:wait()方法会释放锁,而notify()和notify_all()不会释放锁。
示例1:

## 参考https://blog.youkuaiyun.com/luckytanggu/article/details/52183990
import threading
import time

# 商品
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(2)

# 消费者方法
def consume():
    global product
    if con.acquire():
        while True:
            if product is not None:
                print('consume...')
                product = None
                # 通知生产者,商品已经没了
                con.notify()
            # 等待通知
            con.wait()
            time.sleep(2)

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

Semaphore()内置了一个计数器,每次调用acquire()的时候,value-1;每次调用release()的时候,value+1,当计数器的这个value为0时,它会阻塞,然后只能等待其他线程调用release()
示例1:

import threading
import time

s = threading.Semaphore(value = 3)
s.acquire()
print('I am 1')
s.acquire()
print('I am 2')
s.acquire()
print('I am 3')
s.acquire()
print('I am 4')
s.release()
s.release()
s.release()
s.release()

输出如下:

I am 1
I am 2
I am 3

程序不会自动停止,‘I am 4’,也永远不会打印
示例2:

import threading
import time

s = threading.Semaphore(value = 2)
l = threading.Lock()

def test():
	#s.acquire() 去掉注释,或者把s改为l,看看输出效果有什么不一样 
	print(threading.current_thread().name)
	time.sleep(2)
	#s.release() 去掉注释,或者把s改为l,看看输出效果有什么不一样 

t = [threading.Thread(target = test) for i in range(20)]
for i in t:
	i.start()
threading.Event()

一个线程发出事件信号,其他线程等待它。
threading.Event()有一个内部标志,该标志可以用set()方法设置为True,用clear()方法重置为False,wait()方法会阻塞线程,直到标志位True

示例1:

import threading

e = threading.Event()
print(e.is_set())
e.set()
print(e.is_set())
e.clear()
print(e.is_set())

输出如下:

False
True
False
[Finished in 0.3s]

示例2:

import threading
import time

e = threading.Event()

def cooking():
	print('I am cooking')
	e.wait()
	print('I finish cooking')

t = threading.Thread(target = cooking)
t.start()

print('I am sleeping')
time.sleep(3)
e.set()

输出如下:

I am cooking
I am sleeping
I finish cooking
[Finished in 3.2s]
threading.Timer()

这是一个计时器,这个类表示,经过一定的时间后运行的操作。
示例1:

import threading

def hello():
	print('hello, world')

print('I will printing something')
t = threading.Timer(5, hello)
t.start()
# t.cancel() 在计时的过程中,这条语句会取消计时器的操作,被调用的函数不会执行。
threading.local()

threading.local()相当于一个字典,能够储存线程的特定属性。表示线程的局部数据。

示例1:

import threading

mydata = threading.local()
mydata.age = 12
print('age:', mydata.age)

def change():
	mydata.car = 'BMW'
	print(mydata.car)

t = threading.Thread(target = change)
t.start()
t.join()
#print('car:', mydata.car)  #mydata.car只存在于特定的子线程内,去掉注释,会显示不存在mydata.car属性
print('age:', mydata.age)

输出如下:

age: 12
BMW
age: 12
[Finished in 0.2s]

示例2:

import threading

mydata = threading.local()
mydata.age = 12
print('age:', mydata.age)

def change2():
	mydata.age = 'hello'
	print(mydata.age)

t = threading.Thread(target = change2)
t.start()
t.join()
print('age:', mydata.age)

输出如下:

age: 12
hello
age: 12
[Finished in 0.2s]

可以看出,修改过的age属性,也只存在于子线程当中,在主线程中的age没有改变。

Lock,RLock,Condition,Semaphore,BoundedSemaphore对象都能够应用在with语句的上下文管理器中。
import threading

with some_lock:
	# do something...



相当于:
some_lock.acquire():
try:
	# do something...
finally:
	some_lock.release()

参考文章:

https://docs.python.org/3/library/threading.html
http://bbs.51cto.com/thread-1349105-1.html
https://blog.youkuaiyun.com/luckytanggu/article/details/52183990
https://www.cnblogs.com/tkqasn/p/5700281.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值