大纲:https://docs.python.org/3/library/threading.html? 官方文档
此篇文章结合了官方文档,并参考了一些网络资源,加上自己的一些理解,相当于自己的读书笔记,若有错误之处,请指出。
一.python多线程的基本方法
二.多线程返回值问题
三.线程中锁的问题
一.python多线程的基本方法
python中可利用模块threading进行多线程
class threading.
Thread
(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)
target即为callable object,name为定义的进程名,args为要传递给函数的参数,daemon是否为守护线程,默认为非守护线程,若设置daemon=True,则表明在进程退出的时候,不用等待这个线程退出,即随着其它非守护线程的结束,守护线程结束,守护线程用来守护非守护线程,主线程是非守护线程。
start():开始一个线程
join(timeout=None):阻塞该线程
1. 阻塞主进程,专注于执行多线程中的程序。
2. 多线程多join的情况下,依次执行各线程的join方法,前头一个结束了才能执行后面一个。
3. 无参数,则等待到该线程结束,才开始执行下一个线程的join。
4. 参数timeout为线程的阻塞时间,如 timeout=2 就是罩着这个线程2s 以后,就不管他了,继续执行下面的代码。
name
getName
()
setName
()
ident
is_alive
()
daemon
isDaemon
()
setDaemon
()
import sys
import time
from threading import Thread,Lock
import threading
ceshi = 1000
def computesum(n,c):
global ceshi
sum = 0
tname = threading.current_thread().name
ceshi = ceshi - c
for i in range(n):
sum = sum + i
time.sleep(10)
ceshi = ceshi + c
print(ceshi)
return tname
start = time.time()
tdic = {}
lock = Lock()
for i in range(3):
#共创建了3个线程
t = Thread(target=computesum,args=(10000000,i))
#启动线程
t.start()
tdic[i] = t
for i in range(2,-1,-1):
tdic[i].join(timeout=2)
print(i)
end = time.time()
print(end-start)
二.多线程返回值问题
Thread没有给线程返回值的方法,可以通过利用全局变量来传递的方式或者重写Thread的方法实现
import sys
import time
from threading import Thread,Lock
import threading
#重写Thread
class MyThread(Thread):
def __init__(self,target,args=()):
super(MyThread,self).__init__()
self.target = target
self.args = args
def run(self):
self.result = self.target(*self.args)
def get_result(self):
try:
return self.result
except Exception:
return None
ceshi = 1000
#或者此处定义一个全局变量 result
def computesum(n,c):
global ceshi
sum = 0
tname = threading.current_thread().name
ceshi = ceshi - c
for i in range(n):
sum = sum + i
ceshi = ceshi + c
print(ceshi)
#此处将返回的值赋给result
return tname
start = time.time()
tdic = {}
lock = Lock()
for i in range(3):
t = MyThread(target=computesum,args=(10000000,i))
t.start()
tdic[i] = t
for i in range(2,-1,-1):
tdic[i].join()
print(tdic[i].get_result())
end = time.time()
print(end-start)
三.线程中锁的问题
1.Lock
当多个线程修改一个共享数据时,需要进行同步控制操作。
利用lock.acquire()加锁,利用lock.release()释放锁。
每次只要一个线程会获得锁,若一个线程获得锁之后,其它线程试图获取锁,则变成阻塞状态,直到释放锁后,线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁。
acquire
(blocking=True, timeout=-1)
默认blocking=True,即说明一个线程获取锁之后,其它未获得锁的线程会阻塞等待,若blocking=False,则不进行等待,为非阻塞的状态,timeout是设置阻塞等待的最长时间
acquire()的返回值为True or False,表明是否获取到锁?
使用with语句可以进行锁的获取和释放
import sys
import time
from threading import Thread,Lock
import threading
lock = Lock()
ceshi = 0
res = 0
def computesum(n,c):
global ceshi
global res
sum = 0
tname = threading.current_thread().name
if lock.acquire():
print(tname + '获取到')
ceshi = ceshi - c
for i in range(n):
sum = sum + i
ceshi = ceshi + c
print(ceshi,tname)
time.sleep(10)
lock.release()
else:
print(tname+'未获取到')
return tname
start = time.time()
tdic = {}
for i in range(3):
t = Thread(target=computesum,args=(10000000,1))
t.start()
tdic[i] = t
for i in range(2,-1,-1):
tdic[i].join()
print('*'+tdic[i].getName())
end = time.time()
print(end-start)
2.RLock
允许同一线程多次重复上锁,但是注意,上锁的次数必须和释放锁的次数一样,才能真正释放锁,否则会死锁。
使用Lock时,若线程1上锁,若一直不释放锁,线程1再次上锁,会阻塞,但是RLock对相同线程不会出现这种阻塞的情况。
3.Condition
class threading.
Condition
(lock=None)
不指定lock对象,则会生成一个新的RLock对象
condition可利用wait使当前该线程挂起等待,直到另一个线程的notify方法将waiting的线程唤醒。使用condition可以在资源未达到某线程的条件时就一直处于waiting状态,而不需要重复的去acquire锁进行判断,再release锁。注意,notify之后并不是waiting线程就可以去reacquire锁了,一定要notify之后先release锁。
acquire release wait(等待且释放锁) notify(唤醒并不释放锁)
acquire
(*args)
release
()
wait
(timeout=None) #到了timeout时间将会reacquire锁
notify
(n=1)
notify_all
()
import threading
import time
class Producer(threading.Thread):
# 生产者函数
def run(self):
global count
while True:
if con.acquire():
print("生产者获得锁")
if(count>1000):
print("生产者等待")
con.wait(timeout=2)
else:
count = count + 100
print("生产者",count)
if(count>100):
con.notify()
con.release()
time.sleep(1)
class Consumer(threading.Thread):
# 消费者函数
def run(self):
global count
while True:
# 当count 大于等于100的时候进行消费
if con.acquire():
print("消费者获得锁")
if count < 100:
print("消费者等待")
con.wait()
else:
count = count - 50
print("消费者",count)
if(count<1000):
con.notify()
con.release()
time.sleep(1)
# 完成生成后唤醒waiting状态的线程,
# 从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁
count =1000
con = threading.Condition()
def test():
p = Producer()
p.start()
c = Consumer()
c.start()
if __name__ == '__main__':
test()
4.Semaphore
class threading.
Semaphore
(value=1)
信号量是一个计数器,当有一个线程acquire,则计数器减1,release则加1,Semaphore不能小于0,计数器等于0时,其它线程会被阻塞(blocking设置为True时)
信号量通常用于保护数量有限的资源,例如数据库服务器。在资源数量固定的任何情况下,都应该使用有界信号量。在生成任何工作线程前,应该在主线程中初始化信号量。
acquire
(blocking=True, timeout=None)
获取一个信号量。
在不带参数的情况下调用时:
-
如果在进入时,内部计数器的值大于0,将其减1并立即返回true。
-
如果在进入时,内部计数器的值为0,将会阻塞直到被
release()
方法的调用唤醒。一旦被唤醒(并且计数器值大于0),将计数器值减少1并返回true。线程会被每次release()
方法的调用唤醒。线程被唤醒的次序是不确定的。
release
()¶
释放一个信号量,将内部计数器的值增加1。当计数器原先的值为0且有其它线程正在等待它再次大于0时,唤醒正在等待的线程。
import time
import threading
s1=threading.Semaphore(10)
def test():
while True:
if(s1.acquire(blocking=False)):
print("ok",time.ctime(),threading.current_thread().name)
time.sleep(10)
s1.release()
else:
print(threading.current_thread().name,'未获得')
for i in range(20):
t1=threading.Thread(target=test,args=())
t1.start()
5.Event
class threading.
Event
这是线程之间通信的最简单机制之一:一个线程发出事件信号,而其他线程等待该信号。
一个事件对象管理一个内部标志,调用 set()
方法可将其设置为true,调用 clear()
方法可将其设置为false,调用 wait()
方法将进入阻塞直到标志为true。
is_set
()
当且仅当内部标志为true时返回true。
set
()
将内部标志设置为true。所有正在等待这个事件的线程将被唤醒。当标志为true时,调用 wait()
方法的线程不会被被阻塞。
clear
()
将内部标志设置为false。之后调用 wait()
方法的线程将会被阻塞,直到调用 set()
方法将内部标志再次设置为true。
wait
(timeout=None)
阻塞线程直到内部变量为true。如果调用时内部标志为true,将立即返回。否则将阻塞线程,直到调用 set()
方法将标志设置为true或者发生可选的超时。
当提供了timeout参数且不是 None
时,它应该是一个浮点数,代表操作的超时时间,以秒为单位(可以为小数)。
当内部标志在调用wait进入阻塞后被设置为true,或者调用wait时已经被设置为true时,方法返回true。 也就是说,除非设定了超时且发生了超时的情况下将会返回false,其他情况该方法都将返回 True
。
import threading
from threading import Event
def test1(e1, e2):
for item in [1, 3, 5]:
e1.wait()
print(item)
e1.clear()
e2.set()
def test2(e1, e2):
for item in [2, 4, 6]:
e1.wait()
print(item)
e1.clear()
e2.set()
e1, e2 = Event(), Event()
t1 = threading.Thread(target=test1, args=(e1, e2))
t2 = threading.Thread(target=test2, args=(e2, e1))
t1.start()
t2.start()
e1.set()