线程和进程
什么是线程
线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位,一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。(一个线程就是一堆指令集合)
存在以下两种情况线程会停止运行,等待下一次CPU调度执行:
1.CPU调度时间已到;
2.当遇到IO阻塞(如sleep等)
当调度时间已到,这时候CPU就会保存此时线程的执行状态,CPU就会进行上下文切换(即将CPU给其它线程执行)。当CPU再次向该线程调度时,就会读取该状态继续执行该线程
什么是进程
执行的程序就称为一个进程,进程是一堆计算资源(可以有线程,变量等)的集合。
执行一个.py文件就是执行一个进程,这个进程中只有一个线程那就是主线程(如果手动创建线程,进程中就不只只有一个线程);在主线程中创建线程,这些线程和主线程并行运行
进程与线程的区别
1.一个进程中可以存在多个线程,而且多个线程之间可以共享进程的数据;每个进程有自己独立的地址空间;
2.线程之间可以进行资源共享,进程之间不能进行资源共享;
3.线程之间可以互相通信,进程之间不能互相通信;
4.线程可以很容易创建,进程不容易创建,因为创建进程的开销大;
5.线程之间可以互相操作,进程之间不行;
6.主线程可以创建多个线程,主线程可以影响线程;主进程可以创建多个子进程,但是主进程不能影响子进程。
python GIL(Global Interpreter Lock) 全局解释器锁
在cpython解释器中才有GIL,它的作用是:在同一时刻,只能有一个线程进入解释器解释执行,这就导致了python在同一时刻只能使用一颗CPU,不能将线程分给多个CPU来执行即利用不了多核,实现不了多线程;但是可以实现多进程,可以通过多进程将进程分配到不同的CPU上执行(即同一时刻,同一个进程中只能允许一个线程进入解释器解释执行,但是允许不同进程中的线程同时进入解释器解释执行)。
总结:
在python里,如果处理的任务是IO密集型的任务,可以用多线程;如果处理的任务是计算密集型的,建议将多线程部分通过c语言来实现。
threading模块
线程的两种调用方式
直接调用:
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例 t1.start() #启动线程 t2.start() #启动另一个线程 print(t1.getName()) #获取线程名 print(t2.getName())
继承式调用:
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数,这个方法是重写父类的run方法 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start()
线程简单例子
顺序执行:
from time import ctime,sleep def music(func): for i in range(2): print("I was listening to %s. %s" %(func,ctime())) sleep(1) def move(func): for i in range(2): print("I was at the %s! %s" % (func, ctime())) sleep(5) if __name__ == '__main__': music(u'七里香') move(u'世界末路') #############执行完大概用时12s:顺序执行 # I was listening to 七里香. Tue Mar 13 15:49:44 2018 # I was listening to 七里香. Tue Mar 13 15:49:45 2018 # I was at the 世界末路! Tue Mar 13 15:49:46 2018 # I was at the 世界末路! Tue Mar 13 15:49:51 2018
线程执行:
import threading from time import ctime,sleep import time def music(func): for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(1) #遇到IO阻塞,线程就会解除对CPU的占用 print("end listening %s"%ctime()) def move(func): for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] t1 = threading.Thread(target=music,args=('七里香',)) threads.append(t1) t2 = threading.Thread(target=move,args=('阿甘正传',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.start() #############执行完大概用时10s:通过线程执行 # Begin listening to 七里香. Tue Mar 13 15:54:49 2018 # Begin watching at the 阿甘正传! Tue Mar 13 15:54:49 2018 # all over Tue Mar 13 15:54:49 2018 # end listening Tue Mar 13 15:54:50 2018 # Begin listening to 七里香. Tue Mar 13 15:54:50 2018 # end listening Tue Mar 13 15:54:51 2018 # end watching Tue Mar 13 15:54:54 2018 # Begin watching at the 阿甘正传! Tue Mar 13 15:54:54 2018 # end watching Tue Mar 13 15:54:59 2018
join & Daemon
join():
在子线程完成运行之前,这个子线程的父线程将一直被阻塞(即子线程执行完成后,再往下执行该子线程的父线程的后续代码)。
import threading from time import ctime,sleep import time def music(func): for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(1) #遇到IO阻塞,线程就会解除对CPU的占用 print("end listening %s"%ctime()) def move(func): for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] t1 = threading.Thread(target=music,args=('七里香',)) threads.append(t1) t2 = threading.Thread(target=move,args=('阿甘正传',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.start() print ("all over %s" %ctime()) #############执行完大概用时10s:通过线程执行 # Begin listening to 七里香. Tue Mar 13 15:54:49 2018 # Begin watching at the 阿甘正传! Tue Mar 13 15:54:49 2018 # all over Tue Mar 13 15:54:49 2018 # end listening Tue Mar 13 15:54:50 2018 # Begin listening to 七里香. Tue Mar 13 15:54:50 2018 # end listening Tue Mar 13 15:54:51 2018 # end watching Tue Mar 13 15:54:54 2018 # Begin watching at the 阿甘正传! Tue Mar 13 15:54:54 2018 # end watching Tue Mar 13 15:54:59 2018
import threading from time import ctime,sleep import time def music(func): for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(1) #遇到IO阻塞,线程就会解除对CPU的占用 print("end listening %s"%ctime()) def move(func): for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] t1 = threading.Thread(target=music,args=('七里香',)) threads.append(t1) t2 = threading.Thread(target=move,args=('阿甘正传',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.start() t.join() #等t1执行完成后再执行后续的代码 print ("all over %s" %ctime()) #用时12s # Begin listening to 七里香. Tue Mar 13 16:17:13 2018 # end listening Tue Mar 13 16:17:14 2018 # Begin listening to 七里香. Tue Mar 13 16:17:14 2018 # end listening Tue Mar 13 16:17:15 2018 # Begin watching at the 阿甘正传! Tue Mar 13 16:17:15 2018 # end watching Tue Mar 13 16:17:20 2018 # Begin watching at the 阿甘正传! Tue Mar 13 16:17:20 2018 # end watching Tue Mar 13 16:17:25 2018 # all over Tue Mar 13 16:17:25 2018
import threading from time import ctime,sleep import time def music(func): for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(1) #遇到IO阻塞,线程就会解除对CPU的占用 print("end listening %s"%ctime()) def move(func): for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] t1 = threading.Thread(target=music,args=('七里香',)) threads.append(t1) t2 = threading.Thread(target=move,args=('阿甘正传',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.start() t2.join() print ("all over %s" %ctime()) #用时10s # Begin listening to 七里香. Tue Mar 13 16:32:35 2018 # Begin watching at the 阿甘正传! Tue Mar 13 16:32:35 2018 # end listening Tue Mar 13 16:32:36 2018 # Begin listening to 七里香. Tue Mar 13 16:32:36 2018 # end listening Tue Mar 13 16:32:37 2018 # end watching Tue Mar 13 16:32:40 2018 # Begin watching at the 阿甘正传! Tue Mar 13 16:32:40 2018 # end watching Tue Mar 13 16:32:45 2018 # all over Tue Mar 13 16:32:45 2018
import threading from time import ctime,sleep import time def music(func): for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(1) #遇到IO阻塞,线程就会解除对CPU的占用 print("end listening %s"%ctime()) def move(func): for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] t1 = threading.Thread(target=music,args=('七里香',)) threads.append(t1) t2 = threading.Thread(target=move,args=('阿甘正传',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.start() t1.join() print ("all over %s" %ctime()) #用时10s # Begin listening to 七里香. Tue Mar 13 16:39:59 2018 # Begin watching at the 阿甘正传! Tue Mar 13 16:39:59 2018 # end listening Tue Mar 13 16:40:00 2018 # Begin listening to 七里香. Tue Mar 13 16:40:00 2018 # end listening Tue Mar 13 16:40:01 2018 # all over Tue Mar 13 16:40:01 2018 # end watching Tue Mar 13 16:40:04 2018 # Begin watching at the 阿甘正传! Tue Mar 13 16:40:04 2018 # end watching Tue Mar 13 16:40:09 2018
守护进程(Daemon):
将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦 。
import threading from time import ctime,sleep import time def music(func): for i in range(2): print ("Begin listening to %s. %s" %(func,ctime())) sleep(1) #遇到IO阻塞,线程就会解除对CPU的占用 print("end listening %s"%ctime()) def move(func): for i in range(2): print ("Begin watching at the %s! %s" %(func,ctime())) sleep(5) print('end watching %s'%ctime()) threads = [] t1 = threading.Thread(target=music,args=('七里香',)) threads.append(t1) t2 = threading.Thread(target=move,args=('阿甘正传',)) threads.append(t2) if __name__ == '__main__': for t in threads: t.setDaemon(True) t.start() print ("all over %s" %ctime()) # Begin listening to 七里香. Tue Mar 13 17:14:16 2018 # Begin watching at the 阿甘正传! Tue Mar 13 17:14:16 2018 # all over Tue Mar 13 17:14:16 2018
其它方法:
thread 模块提供的其他方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 # 除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法: # run(): 用以表示线程活动的方法。 # start():启动线程活动。 # join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。
同步锁(Lock)
同步锁引入:
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 num-=1 num = 100 #设定一个共享变量 thread_list = [] for i in range(100): #开100个线程去减1 t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num ) #final num:0
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 temp = num num=temp-1 num = 100 #设定一个共享变量 thread_list = [] for i in range(100): #开100个线程去减1 t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num ) #final num:0
import time import threading def addNum(): global num #在每个线程中都获取这个全局变量 temp = num time.sleep(0.0001) num=temp-1 num = 100 #设定一个共享变量 thread_list = [] for i in range(100): #开100个线程去减1 t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: #等待所有线程执行完毕 t.join() print('final num:', num ) #final num: 92 #原因: # 1: why num-=1没问题呢?这是因为动作太快(完成这个动作在切换的时间内) # 2: if sleep(1),现象会更明显,100个线程每一个一定都没有执行完就进行了切换,我们说过sleep就等效于IO阻塞,1s之内不会再切换回来,所以最后的结果一定是99.
工作流程图:
注意:
1: why num-=1没问题呢?这是因为动作太快(完成这个动作在切换的时间内)
2: if sleep(1),现象会更明显,100个线程每一个一定都没有执行完就进行了切换,我们说过sleep就等效于IO阻塞,1s之内不会再切换回来,所以最后的结果一定是99.
多个线程都在同时操作同一个共享资源,所以造成了资源破坏,怎么办呢?
我们会想用join,但join会把整个线程给停住,造成了串行,失去了多线程的意义,而我们只需要把涉及到多线程操作公共数据的时候串行执行,而其它的逻辑还是并行的。
可以通过同步锁来解决这种问题。
import time import threading def addNum(): global num lock.acquire() #启用锁 temp = num #期间的指令执行完成后释放锁 time.sleep(1) num=temp-1 lock.release() #释放锁 num = 100 thread_list = [] lock=threading.Lock() #声明一个同步锁 for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: t.join() print('final num:', num ) #final num: 0
同步锁与GIL的关系:
Python的线程在GIL的控制之下,线程之间,对整个python解释器,对python提供的C API的访问都是互斥的,这可以看作是Python内核级的互斥机制。但是这种互斥是我们不能控制的,我们还需要另外一种可控的互斥机制———用户级互斥。内核级通过互斥保护了内核的共享资源,同样,用户级互斥保护了用户程序中的共享资源。
但是如果你有个操作比如 x += 1,这个操作需要多个bytecodes操作,在执行这个操作的多条bytecodes期间,由于CPU的切换,中途就换thread了,这样就出现了data races的情况了。
线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:(即一把锁被多次require,就会造成死锁)
import threading,time class myThread(threading.Thread): def doA(self): lockA.acquire() print(self.name,"gotlockA",time.ctime()) time.sleep(3) lockB.acquire() print(self.name,"gotlockB",time.ctime()) lockB.release() lockA.release() def doB(self): lockB.acquire() print(self.name,"gotlockB",time.ctime()) time.sleep(2) lockA.acquire() print(self.name,"gotlockA",time.ctime()) lockA.release() lockB.release() def run(self): self.doA() self.doB() if __name__=="__main__": lockA=threading.Lock() lockB=threading.Lock() threads=[] for i in range(5): threads.append(myThread()) for t in threads: t.start() for t in threads: t.join()#等待线程结束,后面再讲。 输出: Thread-1 gotlockA Thu Jun 7 09:28:24 2018 Thread-1 gotlockB Thu Jun 7 09:28:27 2018 Thread-1 gotlockB Thu Jun 7 09:28:27 2018 Thread-2 gotlockA Thu Jun 7 09:28:27 2018
解决方法:使用递归锁
为了支持在同一线程中多次请求同一资源,python提供了“可重用锁”:threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源.
import threading,time class myThread(threading.Thread): def doA(self): lock.acquire() print(self.name,"gotlockA",time.ctime()) time.sleep(3) lock.acquire() print(self.name,"gotlockB",time.ctime()) lock.release() lock.release() def doB(self): lock.acquire() print(self.name,"gotlockB",time.ctime()) time.sleep(2) lock.acquire() print(self.name,"gotlockA",time.ctime()) lock.release() lock.release() def run(self): self.doA() self.doB() if __name__=="__main__": # lockA=threading.Lock() # lockB=threading.Lock() lock = threading.RLock() #递归锁 threads=[] for i in range(5): threads.append(myThread()) for t in threads: t.start() for t in threads: t.join()#等待线程结束,后面再讲。 #输出: # Thread-1 gotlockA Wed Mar 14 11:19:17 2018 # Thread-1 gotlockB Wed Mar 14 11:19:20 2018 # Thread-1 gotlockB Wed Mar 14 11:19:20 2018 # Thread-1 gotlockA Wed Mar 14 11:19:22 2018 # Thread-3 gotlockA Wed Mar 14 11:19:22 2018 # Thread-3 gotlockB Wed Mar 14 11:19:25 2018 # Thread-3 gotlockB Wed Mar 14 11:19:25 2018 # Thread-3 gotlockA Wed Mar 14 11:19:27 2018 # Thread-5 gotlockA Wed Mar 14 11:19:27 2018 # Thread-5 gotlockB Wed Mar 14 11:19:30 2018 # Thread-2 gotlockA Wed Mar 14 11:19:30 2018 # Thread-2 gotlockB Wed Mar 14 11:19:33 2018 # Thread-2 gotlockB Wed Mar 14 11:19:33 2018 # Thread-2 gotlockA Wed Mar 14 11:19:35 2018 # Thread-5 gotlockB Wed Mar 14 11:19:35 2018 # Thread-5 gotlockA Wed Mar 14 11:19:37 2018 # Thread-4 gotlockA Wed Mar 14 11:19:37 2018 # Thread-4 gotlockB Wed Mar 14 11:19:40 2018 # Thread-4 gotlockB Wed Mar 14 11:19:40 2018 # Thread-4 gotlockA Wed Mar 14 11:19:42 2018 #只有当计数器为0时,其它的进程才能进来
import time import threading class Account: def __init__(self, _id, balance): self.id = _id self.balance = balance self.lock = threading.RLock() def withdraw(self, amount): #取钱 with self.lock: self.balance -= amount def deposit(self, amount): #存钱 with self.lock: self.balance += amount def drawcash(self, amount):#lock.acquire中嵌套lock.acquire的场景 with self.lock: interest=0.05 count=amount+amount*interest self.withdraw(count) def transfer(_from, to, amount): #转账 #锁不可以加在这里 因为如果有外面的其它的函数在调用withdraw和deposit的情况下,同样是不安全的。所以锁应该加在函数内 _from.withdraw(amount) to.deposit(amount) alex = Account('alex',1000) yuan = Account('yuan',1000) t1=threading.Thread(target = transfer, args = (alex,yuan, 100)) t1.start() t2=threading.Thread(target = transfer, args = (yuan,alex, 200)) t2.start() t1.join() t2.join() print('>>>',alex.balance) print('>>>',yuan.balance)
信号量(也是一种锁)
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
同步锁和信号量的区别:
同步锁:只能允许一个线程,最外层锁未释放,其它线程无法进入;
信号量:可以并发执行线程,内部有一个计数器,每当调用acquire()时-1,调用release()时+1。
实例:
import threading,time class myThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(5) semaphore.release() if __name__=="__main__": semaphore=threading.Semaphore(5) #创建一个信号量,参数就是定义最多允许多少个线程并行执行 thrs=[] for i in range(23): thrs.append(myThread()) for t in thrs: t.start() #输出: # Thread-1 # Thread-2 # Thread-3 # Thread-4 # Thread-5 # Thread-6 # Thread-8 # Thread-9 # Thread-7 # Thread-10 # Thread-11 # Thread-12 # Thread-14 # Thread-13 # Thread-15 # Thread-18 # Thread-16 # Thread-17 # Thread-19 # Thread-20 # Thread-21 # Thread-22 # Thread-23
条件变量同步(Condition),也是一种锁,可以实现线程间通信的作用
有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。
lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传人锁,默认自动创建一个RLock()。
wait():条件不满足时调用,线程会释放锁并进入等待阻塞;
notify():条件创造后调用,通知等待池激活一个线程;
notifyAll():条件创造后调用,通知等待池激活所有线程。
实例:
import threading,time from random import randint class Producer(threading.Thread): def run(self): global L while True: val=randint(0,100) print('生产者',self.name,":Append"+str(val),L) if lock_con.acquire(): L.append(val) lock_con.notify() #条件创造后调用,通知等待池激活一个线程 lock_con.release() time.sleep(3) class Consumer(threading.Thread): def run(self): global L while True: lock_con.acquire() if len(L)==0: lock_con.wait() #如果len(L)=0条件成立,消费者这个线程就会会释放锁并进入等待阻塞,即线程进入等待池 print('消费者',self.name,":Delete"+str(L[0]),L) del L[0] lock_con.release() time.sleep(0.25) if __name__=="__main__": L=[] lock_con=threading.Condition() #创建一个条件变量同步锁 threads=[] for i in range(5): threads.append(Producer()) threads.append(Consumer()) #这时会有6个线程对象 for t in threads: t.start() for t in threads: t.join()
条件同步(Event):不是锁
条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境中。event=threading.Event():条件环境对象,event的初始状态值为False;
event.isSet():返回event的状态值。
event.wait():如果event.isSet()==false将阻塞线程。
event.set():设置evevt的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度。
event.clear():恢复event的状态值为flase。
实例一:
import threading,time class Boss(threading.Thread): def run(self): print("BOSS:今晚大家都要加班到22:00。") event.isSet() or event.set() time.sleep(5) print("BOSS:<22:00>可以下班了。") event.isSet() or event.set() class Worker(threading.Thread): def run(self): event.wait() #开始event.isSet()==False,线程阻塞 print("Worker:哎……命苦啊!") time.sleep(0.25) event.clear() event.wait() print("Worker:OhYeah!") if __name__=="__main__": event=threading.Event() threads=[] for i in range(5): threads.append(Worker()) threads.append(Boss()) for t in threads: t.start() for t in threads: t.join() #通过event的标准位,进行线程间通信 #输出 # BOSS:今晚大家都要加班到22:00。 # Worker:哎……命苦啊! # Worker:哎……命苦啊! # Worker:哎……命苦啊! # Worker:哎……命苦啊! # Worker:哎……命苦啊! # BOSS:<22:00>可以下班了。 # Worker:OhYeah! # Worker:OhYeah! # Worker:OhYeah! # Worker:OhYeah! # Worker:OhYeah!
实例二:
import threading,time import random def light(): if not event.isSet(): event.set() #wait就不阻塞 #绿灯状态 count = 0 while True: if count < 10: print('