python 多线程
线程简介
线程是CPU分配资源的基本单位。但一个程序开始运行,这个程序就变成了一个进程,而一个进程相当于一个或者多个线程。当没有多线程编程时,一个进程也是一个主线程,但有多线程编程时,一个进程包含多个线程和一个主线程。使用线程可以实现程序的并发。
线程特点
- 每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。
- 指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。
- 线程可以被抢占(中断)。
- 在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。
多线程优势
- 使用线程可以把占据长时间的程序中的任务放到后台去处理。
- 用户界面可以更加吸引人,比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
- 程序的运行速度可能加快
- 在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等
线程分类
- 内核线程:由操作系统内核创建和撤销。
- 用户线程:不需要内核支持而在用户程序中实现的线程。
线程模块
- _thread
- threading(推荐使用)
更多多线程理论参考:http://www.cnblogs.com/linhaifeng/articles/7430082.html
线程开启
方式一:创建线程要执行的函数,把这个函数传递进Thread对象里,让它来执行
from threading import Thread import time def test(param): time.sleep(2) print('Hello %s'%param) if __name__ == '__main__': t=Thread(target=test,args=('World',)) t.start() print('main threading')
方式二:继承thread类,重写run方法与Java的多线程非常类似
from threading import Thread import time class Test_Thread(Thread): def __init__(self,param): super().__init__() self.param=param def run(self): time.sleep(2) print('Hello %s' % self.param) if __name__ == '__main__': t = Test_Thread('Python') t.start() print('main Threading')
线程方法
Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enerate())有相同的结果。
join()方法
主线程x中,创建了子线程y,并且在主线程x中调用了y.join(),则主线程x会在调用的地方等待,直到子线程y完成操作后,才会接着往下执行。(对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕主线程等待子线程结束)
from threading import Thread import time def test(param): time.sleep(2) print('Hello %s' %param) if __name__ == '__main__': t=Thread(target=test,args=('Python',)) t.start() t.join() print('main threading') print(t.is_alive())
setDaemon()方法
主线程x中创建了子线程y,并且在主线程x中调用了y.setDaemon(),则子线程y设置为守护线程,如果主线程x执行束,不论子线程y是否完成,一并和主线程x退出.
ps:必须在start() 方法调用之前设置,如果不设置为守护线程,程序会被无限挂起。(主线程不等待子线程结束即守护线程)
from threading import Thread import time def test(param): time.sleep(2) print('Hello %s' %param) if __name__ == '__main__': t=Thread(target=sayhi,args=('World',)) # 必须在t.start()之前设置 t.setDaemon(True) t.start() print('main threding') print(t.is_alive())
GIL VS Lock
Python已经存在一个GIL但是来保证同一时间只能有一个线程来执行了,但是为什么这里还需要lock? 首先需要明确的是锁的目的是为了保护共享的数据,保证同一时间只能有一个线程来修改共享的数据而且保护不同的数据就应该加不同的锁。GIL与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock。
线程同步
如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间即可
同步锁
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁
import threading R=threading.Lock() R.acquire() ''' 对公共数据的操作 ''' R.release()
#并发执行变成串行,牺牲了执行效率保证了数据安全 from threading import Thread,Lock import os,time def work(): global nbers lock.acquire() temp=nbers time.sleep(0.1) nbers=temp-1 lock.release() if __name__ == '__main__': lock=Lock() nbers=66 list=[] for i in range(66): t=Thread(target=work) list.append(t) t.start() for t in list: t.join() print(nbers)
过程分析:
第一步:66个线程去抢GIL锁,即抢执行权限
第二部:肯定有一个线程先抢到GIL(暂且称为线程一),然后开始执行,一旦执行就会拿到lock.acquire()
第三步:极有可能线程一还未运行完毕,就有另外一个线程二抢到GIL,然后开始运行,但线程二发现互斥锁lock还未被线程一释放,于是阻塞,被迫交出执行权限,即释放GIL
第四步:直到线程一重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复以上的过程
互斥锁与join()方法的区别
加锁会让运行变成串行,那么如果在start之后立即使用join,就不需要加锁而且也是串行的效果。
但是在start之后立刻使用join,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,数据是安全的。
但问题是start之后立即join:任务内的所有代码都是串行执行的,而加锁只是加锁的部分即修改共享数据的部分是串行的,这就是两者的区别。
如果单从保证数据安全方面,二者都可以实现,但很明显是加锁的程序执行效率显然比使用join()方法更高
*不加锁:并发执行,速度快,但是数据是不安全 from threading import current_thread,Thread,Lock import os,time def task(): global n print('%s is running' %current_thread().getName()) temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ---------------------------------------------------- Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:0.5216062068939209 n:99 *不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,但是数据安全 from threading import current_thread,Thread,Lock import os,time def task(): #未加锁的代码并发运行 time.sleep(3) print('%s start to run' %current_thread().getName()) global n #加锁的代码串行运行 lock.acquire() temp=n time.sleep(0.5) n=temp-1 lock.release() if __name__ == '__main__': n=100 lock=Lock() threads=[] start_time=time.time() for i in range(100): t=Thread(target=task) threads.append(t) t.start() for t in threads: t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ---------------------------------------------------------- Thread-1 is running Thread-2 is running ...... Thread-100 is running 主:53.294203758239746 n:0 *使用join()方法,数据安全,但是串行执行 from threading import current_thread,Thread,Lock import os,time def task(): time.sleep(3) print('%s start to run' %current_thread().getName()) global n temp=n time.sleep(0.5) n=temp-1 if __name__ == '__main__': n=100 lock=Lock() start_time=time.time() for i in range(100): t=Thread(target=task) t.start() t.join() stop_time=time.time() print('主:%s n:%s' %(stop_time-start_time,n)) ----------------------------------------- Thread-1 start to run Thread-2 start to run ...... Thread-100 start to run 主:350.6937336921692 n:0 #耗时是多么的恐怖
死锁
是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,如果没有外力进行干预则程序就无法继续执行,此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程就称为死锁进程
from threading import Thread,Lock import time X=Lock() Y=Lock() class Lock_Thread(Thread): def run(self): self.actionX() self.actionY() def actionX(self): X.acquire() print('\033[41m%s 拿到X锁\033[0m' %self.name) Y.acquire() print('\033[42m%s 拿到Y锁\033[0m' %self.name) Y.release() X.release() def actionY(self): Y.acquire() print('\033[43m%s 拿到Y锁\033[0m' %self.name) time.sleep(2) X.acquire() print('\033[44m%s 拿到X锁\033[0m' %self.name) X.release() Y.release() if __name__ == '__main__': for i in range(10): t=Lock_Thread() t.start() ---------------------------------------------------------- Thread-1 拿到X锁 Thread-1 拿到Y锁 Thread-1 拿到Y锁 Thread-2 拿到X锁 然后就卡住,死锁了
递归锁
即死锁解决方法,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。
这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,
其他的线程才能获得资源。如果使用RLock代替Lock,则不会发生死锁.
即X=Y=threading.RLock()如果一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,
即counter递减到0为止
import threading import time class Lock_Thread(threading.Thread): def actionX(self): r_lcok.acquire() #count=1 print(self.name,"gotX",time.ctime()) time.sleep(2) r_lcok.acquire() #count=2 print(self.name, "gotY", time.ctime()) time.sleep(1) r_lcok.release() #count=1 r_lcok.release() #count=0 def actionY(self): r_lcok.acquire() print(self.name, "gotY", time.ctime()) time.sleep(2) r_lcok.acquire() print(self.name, "gotX", time.ctime()) time.sleep(1) r_lcok.release() r_lcok.release() def run(self): self.actionX() self.actionY() if __name__ == '__main__': r_lcok=threading.RLock() L=[] for i in range(5): t=Lock_Thread() t.start() L.append(t) for i in L: i.join() print("ending....")
信号量Semahpore
Semaphore管理一个内置的计数器,每当调用acquire()时内置计数器-1;调用release() 时内置计数器+1;计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
ps:信号量与进程池是完全不同的概念,进程池Pool(4),最大只能产生4个进程,而且从头到尾都只是这四个进程,不会产生新的,而信号量是产生一堆线程/进程
互斥锁与信号量推荐博客:http://url.cn/5DMsS9r
*方法一 import threading import time semaphore = threading.Semaphore(6) def func(): if semaphore.acquire(): print (threading.currentThread().getName() + ' get semaphore') time.sleep(2) semaphore.release() for i in range(20): t = threading.Thread(target=func) t.start() --------------------------------------------- *方法二 from threading import Thread,Semaphore import threading import time def func(): sm.acquire() print('%s get sm' %threading.current_thread().getName()) time.sleep(3) sm.release() if __name__ == '__main__': sm=Semaphore(6) for i in range(26): t=Thread(target=func) t.start()
Event(标志位)
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其它线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就 会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
Event方法
event.isSet():返回event的状态值; event.wait():如果 event.isSet()==False将阻塞线程; event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False
举例:多个工作线程尝试链接MySQL,如果想要在链接前确保MySQL服务正常才让那些工作线程去连接MySQL服务器,若连接不成功,都会去尝试重新连接。那么就可以采用threading.Event机制来协调各个工作线程的连接操作 from threading import Thread,Event import threading import time,random def conn_mysql(): count=1 while not event.is_set(): if count > 3: raise TimeoutError('链接超时') print('<%s>第%s次尝试链接' % (threading.current_thread().getName(), count)) event.wait(0.5) count+=1 print('<%s>链接成功' %threading.current_thread().getName()) def check_mysql(): print('\033[45m[%s]正在检查mysql\033[0m' % threading.current_thread().getName()) time.sleep(random.randint(2,4)) event.set() if __name__ == '__main__': event=Event() connX=Thread(target=conn_mysql) connY=Thread(target=conn_mysql) check=Thread(target=check_mysql) connX.start() connY.start() check.start()
线程队列Queue
Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列 PriorityQueue。
而且这些队列都实现了锁原语,能够在多线程中直接使用,可以使用队列来实现线程间的同步。
Queue 方法
Queue.qsize() 返回队列的大小
Queue.empty() 如果队列为空,返回True,反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.full 与 maxsize 大小对应
Queue.get([block[, timeout]])获取队列,timeout等待时间
Queue.get_nowait() 相当Queue.get(False)
Queue.put(item) 写入队列,timeout等待时间
Queue.put_nowait(item) 相当Queue.put(item, False)
Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
Queue.join() 实际上意味着等到队列为空,再执行别的操作
先进先出 import queue q=queue.Queue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) -------------------- first second third 后进先出 import queue q=queue.LifoQueue() q.put('first') q.put('second') q.put('third') print(q.get()) print(q.get()) print(q.get()) ------------------- third second first 优先级的队列 import queue q=queue.PriorityQueue() #put进入一个元组,元组的第一个元素是优先级(通常是数字,也可以是非数字之间的比较),数字越小优先级越高 q.put((20,'a')) q.put((10,'b')) q.put((30,'c')) print(q.get()) print(q.get()) print(q.get()) ------------------- (10, 'b') (20, 'a') (30, 'c')
生产者与消费者模型
import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <10: print("making........") time.sleep(5) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 #q.task_done() q.join() print("ok......") def Consumer(name): count = 0 while count <10: time.sleep(random.randrange(4)) # if not q.empty(): # print("waiting.....") #q.join() data = q.get() print("eating....") time.sleep(4) q.task_done() #print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) # else: # print("-----no baozi anymore----") count +=1 p = threading.Thread(target=Producer, args=('jack',)) cX = threading.Thread(target=Consumer, args=('tom',)) cY = threading.Thread(target=Consumer, args=('rus',)) cZ = threading.Thread(target=Consumer, args=('kia',)) p.start() cX.start() cY.start() cZ.start()
定时器Timer
from threading import Timer def times(): print("Hello World") t = Timer(1, times) # After 1 seconds, "Hello World" will be printed t.start()