我们先来分析一下进程,线程所涉及的内容
串行,发行,并行,阻塞,不阻塞
单进程,多进程,进程锁,进程信号量 ,进程queue(队列),进程池
单线程,多线程,线程锁,线程信号量(semaphore),事件(event),线程queue(队列),线程池
io操作不占用cpu
计算占用cpu
python多线程,不适合cpu密集操作型的任务,适合io操作密集型。
1 import threading 2 import time 3 def fun(n): 4 print("this is %s"%n) 5 time.sleep(2) 6 print("%s is done"%n) 7 t1 = threading.Thread(target=fun,args=("1",)) 8 t1.start()
1 import threading 2 import time 3 def fun(n): 4 print("this is %s"%n) 5 time.sleep(2) 6 print("%s is done"%n) 7 for i in range(10): 8 t1 = threading.Thread(target=fun,args=("1",)) 9 t1.start()
守护线程
守护线程,就是线程会守护主线程,主线程不用等到守护线程的结束,就可以直接结束线程
1 import threading 2 import time 3 def run(num): 4 print("---starting",num) 5 time.sleep(2) 6 print("---done---") 7 def main(): 8 print("主线程开始") 9 for i in range(2): 10 t1 = threading.Thread(target=run,args=(i,)) 11 t1.start() 12 print("启动线程",t1.getName()) 13 #t1.join() 14 print('结束主线程') 15 m = threading.Thread(target=main,args=()) 16 m.setDaemon(True)#设置主线程为守护线程 17 m.start() 18 m.join(timeout=3) 19 print('end......')
线程同步(线程锁)
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。如下:
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。
考虑这样一种情况:一个列表里所有元素都是0,线程”set”从后向前把所有元素改成1,而线程”print”负责从前往后读取列表并打印。
那么,可能线程”set”开始改的时候,线程”print”便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。
锁有两种状态——锁定和未锁定。每当一个线程比如”set”要访问共享数据时,必须先获得锁定;如果已经有别的线程比如”print”获得锁定了,那么就让线程”set”暂停,也就是同步阻塞;等到线程”print”访问完毕,释放锁以后,再让线程”set”继续。
经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。
锁提供如下方法:
1.Lock.acquire([blocking])
2.Lock.release()
3.threading.Lock() 加载线程的锁对象,是一个基本的锁对象,一次只能一个锁定,其余锁请求,需等待锁释放后才能获取
4.threading.RLock() 多重锁,在同一线程中可用被多次acquire。如果使用RLock,那么acquire和release必须成对出现, 调用了n次acquire锁请求,则必须调用n次的release才能在线程中释放锁对象。
1 import threading 2 import time 3 class Mythreading(threading.Thread): 4 def __init__(self,threadID,threadName): 5 super(Mythreading, self).__init__() 6 self.threadID = threadID 7 self.threadName = threadName 8 def run(self): 9 print("线程开始",self.threadName) 10 threadLock.acquire() #获取线程锁 11 for i in range(3): 12 time.sleep(2) 13 print('%s,,,%s' % (time.asctime(time.localtime(time.time())), self.threadName)) 14 threadLock.release()#释放线程锁 15 threadLock = threading.Lock() #创建线程锁 16 t1 = Mythreading(1,"thread1") 17 t2 = Mythreading(2,"thread2") 18 t1.start() 19 t2.start()
结果:
线程开始 thread1 线程开始 thread2 Tue Mar 24 16:11:27 2020,,,thread1 Tue Mar 24 16:11:29 2020,,,thread1 Tue Mar 24 16:11:31 2020,,,thread1 Tue Mar 24 16:11:33 2020,,,thread2 Tue Mar 24 16:11:35 2020,,,thread2 Tue Mar 24 16:11:37 2020,,,thread2
如果不加入锁的话,程序应该是不阻塞的,但是加入锁之后,发现变成串行了。
多重锁(RLock)
Lock和RLock的区别:
Lock:Lock被称为①原始锁,原始锁是一个②在锁定时不属于特定线程的同步基元组件,它是能用的最低级的同步基元组件。原始锁处于 "锁定" 或者 "非锁定" 两种状态之一。它被创建时为非锁定状态。它有两个基本方法, acquire() 和 release() 。当状态为非锁定时, acquire() 将状态改为锁定并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError 异常。锁支持 上下文管理协议,即支持with语句,下文例子中会用到。
RLock:RLock被称为重入锁,若要锁定锁,线程调用其 acquire() 方法;一旦线程拥有了锁,方法将返回。若要解锁,线程调用 release() 方法。 ③acquire()/release() 对可以嵌套,重入锁必须由获取它的线程释放。一旦线程获得了重入锁,同一个线程再次获取它将不阻塞。只有最终 release() (最外面一对的 release() ) 将锁解开,才能让其他线程继续处理 acquire() 阻塞。;线程必须在每次获取它时释放一次。
两者使用的方法大部分还是相同的,下面根据以上红色强调部分描述一下二者的区别
①是名称的区别,一个叫原始锁,一个叫重入锁,这没啥好说的
②Lock在锁定时不属于特定线程,也就是说,Lock可以在一个线程中上锁,在另一个线程中解锁。而对于RLock来说,只有当前线程才能释放本线程上的锁,即解铃还须系铃人:
1 import threading 2 import time 3 4 lock = threading.Lock() 5 lock.acquire() 6 7 def func(): 8 lock.release() 9 print("lock is released") 10 11 t = threading.Thread(target=func) 12 t.start() 13 14 输出结果为:lock is released
上面代码中,在主线程中创建锁,并上锁,但是是在t线程中释放锁,结果正常输出,说明一个线程上的锁,可以由另外线程解锁。如果把上面的锁改为RLock则报错
③也是这两者最大的区别了,RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。
1 import threading 2 3 rlock = threading.RLock() 4 5 def func(): 6 if rlock.acquire(): # 第一把锁 7 print("first lock") 8 if rlock.acquire(): # 第一把锁没解开的情况下接着上第二把锁 9 print("second lock") 10 rlock.release() # 解开第二把锁 11 rlock.release() # 解开第一把锁 12 13 14 t = threading.Thread(target=func) 15 t.start() 16 17 输出结果: 18 first lock 19 second lock
注意上面强调的同一线程中,因为对于RLock来说只有当前线程才能释放本线程上的锁,并不能在t1线程中已经执行rlock.acquire,且未释放锁的情况下,在另一个t2线程中还能执行rlock.acquire(这种情况会导致t2阻塞)
为什么有lock,还要RLock
看例子:
1 import threading 2 import time 3 4 lock1 = threading.RLock() 5 6 def inner(): 7 with lock1: 8 print("inner1 function:%s" % threading.current_thread()) 9 10 def outer(): 11 print("outer function:%s" % threading.current_thread()) 12 with lock1: 13 inner() 14 15 if __name__ == "__main__": 16 t1 = threading.Thread(target=outer) 17 t2 = threading.Thread(target=outer) 18 t1.start() 19 t2.start() 20 结果: 21 22 outer function:<Thread(Thread-1, started 12892)> 23 inner1 function:<Thread(Thread-1, started 12892)> 24 outer function:<Thread(Thread-2, started 7456)> 25 inner1 function:<Thread(Thread-2, started 7456)>
首先只看t1线程,当执行到outer函数时,首先打印outer function:<Thread(Thread-1, started 12892)>,然后用lock1给当前线程上了一把锁,然后执行inner函数,在inner里面又需要用lock1来上一把锁,如果此时用Lock的话,由于已经上了一把锁,程序会因为第二次无法获得锁而导致t1阻塞,程序阻塞之后又没法释放锁,所以会导致程序死锁。这种情况下,RLock就派上用场了。
信号量(semaphore)
Lock锁是同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,如一个场所内只有3把椅子给人坐,那么只允许3个人,其它人则排队,只有等里面的人出来后才能进去。
1 import threading 2 3 def foo(): 4 time.sleep(2) #程序休息2秒 5 print("ok",time.ctime()) 6 7 for i in range(20): 8 t1=threading.Thread(target=foo,args=()) #实例化一个线程 9 t1.start() #启动线程
哇,我的电脑好厉害,一口气就执行20个线程。
如果在主机执行IO密集型任务的时候再执行这种类型的程序时,比如几百万个线程,那么计算机就有很大可能会宕机(死机)。
这时候就可以为这段程序添加一个计数器功能,来限制一个时间点内的线程数量。
1 import time 2 import threading 3 4 s1=threading.Semaphore(5) #添加一个计数器 5 6 def foo(): 7 s1.acquire() #计数器获得锁 8 time.sleep(5) #程序休眠2秒 9 print("ok",time.ctime()) 10 s1.release() #计数器释放锁 11 12 13 for i in range(20): 14 t1=threading.Thread(target=foo,args=()) #创建线程 15 t1.start() #启动线程
这样线程一口气执行5个线程,直到20个线程都结束。
python(event)
python线程的事件用于主线程控制其它线程的执行,事件主要提供了三个方法:
event.wait 线程阻塞
event.set 将全局对象“Flag”设置为False
event.clear 将全局对象"Flag"设置为True
事件处理的机制:全局定义了一个“Flag”,默认为“False”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞。
浙公网安备 33010602011771号