我们先来分析一下进程,线程所涉及的内容

串行,发行,并行,阻塞,不阻塞

单进程,多进程,进程锁,进程信号量 ,进程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 方法时便不再阻塞。

 

posted on 2020-03-12 10:55  学习永远没有尽头  阅读(131)  评论(0)    收藏  举报