线程
关于线程的理论知识,参见:http://www.cnblogs.com/Eva-J/articles/8306047.html
导引
import time from threading import Thread import os def func(n): time.sleep(1) print(n,os.getppid()) for i in range(10): t=Thread(target=func,args=(i,)) t.start() print('主线程:',os.getppid())
同时也能清晰的看出,主线程和子线程在同一个进程中。
在这段代码中,清晰的体现出了多线程的效果。如果没有多线程,那么所有的执行结果将会每隔1秒停顿后出来。但是实际运行结果是所有的执行结果都全部出来以后,才整体停顿。
开启多线程的另外一种形式,这里和进程的方法一致
import time from threading import Thread def func(n): time.sleep(1) print(n) class MyThread(Thread): def __init__(self,arg): super().__init__() self.arg=arg def run(self): time.sleep(1) print(self.arg) for i in range(10): t=MyThread(i) t.start()
进阶
import time from threading import Thread import os def func(n): global g g=0 time.sleep(1) print(n,os.getppid()) g=100 t_list=[] for i in range(10): t=Thread(target=func,args=(i,)) t.start() t_list.append(t) for i in t_list: t.join() print(g)#可以看到最终g的结果依旧是0,这是因为在同一个进程内设置了全局变量g=0,所以主线程中的结果依旧是0
进程是最小的内存分配单位
线程是操作系统调度的最小单位,
进程内至少含有一个线程
进程中可以开启多个线程:开启一个线程所需要的时间远远小于开启一个进程所需要的时间。
多个线程内部有自己的数据栈,数据不共享
全局变量在多个线程之间是共享的
全局解释器:GIL
由于一个cpu能够调用多个cpu线程,那么就会导致数据的不安全,此时Cpython解释器就加了一把锁,这把锁的作用就是在同一时间只能有一个线程访问CPU,这把锁就叫做全局解释器。
这把锁实际上锁的是线程,这把锁的存在导致同一时间只能由一个线程运行在cpu上,导致不能有两个线程在cpu上跑,降低了cpu的运行效率。
这把锁不是python语言的问题而是Cpython解释器导致的。
线程的出现是最大限度的利用cpu。
注意java等编译型语言不存在,因为是全部读完才会执行;
而python、PHP等解释型语言是读一行,执行一行,就会面临这样的问题。
全局解释器锁GIL
Python代码的执行由Python虚拟机(也叫解释器主循环)来控制。Python在设计之初就考虑到要在主循环中,同时只有一个线程在执行。虽然 Python 解释器中可以“运行”多个线程,但在任意时刻只有一个线程在解释器中运行。
对Python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
a、设置 GIL;
b、切换到一个线程去运行;
c、运行指定数量的字节码指令或者线程主动让出控制(可以调用 time.sleep(0));
d、把线程设置为睡眠状态;
e、解锁 GIL;
d、再次重复以上所有步骤。
在调用外部代码(如 C/C++扩展函数)的时候,GIL将会被锁定,直到这个函数结束为止(由于在这期间没有Python的字节码被运行,所以不会做线程切换)编写扩展的程序员可以主动解锁GIL。
Cpython解释器下的pyhon程序在同一时刻,多个线程中只能有一个线程被cpu执行。
但是在日常应用中一般更多的是高IO类型的问题:因为IO操作一般不需要cpu执行太多的功能
爬取200个网页
qq聊天
处理日志文件
处理web请求
读写数据库
等等。所以这些应用一般都不涉及全局锁
多线程和多进程效率的对比
import time from threading import Thread from multiprocessing import Process def func(n): n+1 if __name__=='__main__': start1=time.time() t_list=[] for i in range(100): t=Thread(target=func,args=(i,)) t.start() t_list.append(t) for t in t_list: t.join() t1=time.time()-start1 start2=time.time() t_list=[] for i in range(100): t=Process(target=func,args=(i,)) t.start() t_list.append(t) for t in t_list: t.join() t2=time.time()-start2 print(t1,t2) 结果:t1=0.015596151351928711 t2=8.438106298446655, 可见t1的效率远高于t2
多线程使用input方法
from threading import Thread def demo(n): inp=input('%s:'%n) print(inp) for i in range(5): t=Thread(target=demo,args=('name%s'%i,)) t.start()
利用多线程实现socket聊天
sever
import socket from threading import Thread def chat(conn): conn.send(b'hello') msg = conn.recv(1024).decode('utf-8') print(msg) conn.close() sk=socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while 1: conn,addr=sk.accept() t=Thread(target=chat,args=(conn,)) t.start() sk.close()
clent
import socket from threading import Thread sk=socket.socket() sk.connect(('127.0.0.1',8080)) msg=sk.recv(1024).decode('utf-8') print(msg) msg1=input('>>>:').encode('utf-8') sk.send(msg1) sk.close()
threading的其他方法
import threading import time def func(n): time.sleep(0.5) print(n,threading.current_thread(),threading.get_ident())#得到当期线程的线程名,编号 for i in range(10): threading.Thread(target=func,args=(i,)).start() print(threading.active_count())#活着的线程数,答案是11个,因为除了子线程以外还有主线程 print(threading.current_thread()) print(threading.enumerate())#将所有线程构成一个列表
守护线程
import time from threading import Thread def func1(): while 1: print('*'*10) time.sleep(1) def func2(): print('func2') t1=Thread(target=func1,) t1.damean=True t1.start() print('123') t2=Thread(target=func2,) t2.start() print('主线程')
守护进程随着主进程代码的执行结束而结束
守护线程会在主线程结束之后等待其他子线程的结束而结束
原因:
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
线程锁
import time from threading import Thread def func(): global n temp=n time.sleep(0.2) n=temp-1 n=10 t_list=[] for i in range(10): t=Thread(target=func) t.start() t_list.append(t) for t in t_list:t.join() print(n)#结果是9
这是因为即使是线程中存在GIL锁,但是由于由于这个锁是加在线程上的,保证同一时间只能有一个线程访问CPU,但是当第一个线程取了10以后,cpu的时间控制片恰好转过来,第一个线程还没来得及把结果9放回去,第二个线程又去取了10,这样结果还是9.
import time from threading import Thread,Lock def func(): global n lock.acquire() temp=n time.sleep(0.2) n=temp-1 lock.release() n=10 t_list=[] lock=Lock() for i in range(10): t=Thread(target=func) t.start() t_list.append(t) for t in t_list:t.join() print(n)
加上了锁,保证结果为0.
科学家吃面问题
import time from threading import Thread,Lock lock1=Lock() lock2=Lock() def func1(name): lock1.acquire() print('%s拿到面条啦'%name) time.sleep(1) lock2.acquire() print('%s拿到叉子啦'%name) print('%s可以吃面啦'%name) lock1.release() lock2.release() def func2(name): lock2.acquire() print('%s拿到叉子啦' % name) lock1.acquire() print('%s拿到面条啦'%name) print('%s可以吃面啦'%name) lock2.release() lock1.release() Thread(target=func1,args=('tom',)).start() Thread(target=func2,args=('Tom',)).start() Thread(target=func1,args=('JACK',)).start() Thread(target=func2,args=('jack',)).start()
只有同时拿到叉子和面条才可以,上面的例子很容易一个人拿着叉子,一个人拿到面条,从而产生死锁。
那么科学家吃面问题,不能使用常规的锁来解决,这就需要用到递归锁RLOCK
递归锁就好像是要拥有很多个门的屋子,要进入最里面的,需要使用多个钥匙才能进去。也就说,在第一个人拿着这串钥匙依次进入很多个门出来还回钥匙之前,其他人不能使用钥匙进入房间
import time from threading import Thread,RLock lock1=lock2=RLock()#lock1和lock2是一串钥匙上的两把钥匙 def func1(name): lock1.acquire()#拿到第一把钥匙,也就意味着拿到一整串钥匙 print('%s拿到面条啦'%name) time.sleep(1) lock2.acquire() print('%s拿到叉子啦'%name) print('%s可以吃面啦'%name) lock1.release() lock2.release() def func2(name): lock2.acquire() print('%s拿到叉子啦' % name) lock1.acquire() print('%s拿到面条啦'%name) print('%s可以吃面啦'%name) lock2.release() lock1.release() Thread(target=func1,args=('tom',)).start() Thread(target=func2,args=('Tom',)).start() Thread(target=func1,args=('JACK',)).start() Thread(target=func2,args=('jack',)).start()
使用递归锁,完美的解决了上面的这个问题。
LOCK又叫做互斥锁
一般进程之间的数据就不共享,所以在进程哪里就很少使用迭代锁。
信号量
import time from threading import Thread,Semaphore def func(a,b): sem.acquire() time.sleep(1) print(a+b) sem.release() sem=Semaphore(2) for i in range(10): t=Thread(target=func,args=(i,i+5)) t.start()
事件
场景:
连接数据库:需要两个线程
第一个线程:连接数据库;等待一个信号,告诉我们之间的网络是联通的;连接数据库
第二个线程:检测与数据库之间的网络是否联通;time.sleep(0,2);将事件的状态设置为True
假设:当第一个线程触发,即连接上数据库以后,第二个线程开始运行
import time import random from threading import Thread,Event def connect_db(): while 1: e.wait()#这里再等待check_web线程的信号,信号为true的时候触发这里 print('连接数据库') def check_web(): time.sleep(random.randint(0,2)) e.set() e=Event() t1=Thread(target=connect_db) t2=Thread(target=check_web) t1.start() t2.start()
但是上面的代码有个问题,当第一个线程迟迟等不到第二个线程的信号的时候,会一直在等待,那么
import time import random from threading import Thread,Event def connect_db(): while 1: e.wait(1)#这里再等待check_web线程的信号,只等待1秒 if e.is_set()==True: print('连接数据库') break else: print('连接失败') break def check_web(): time.sleep(random.randint(0,2)) e.set() e=Event() t1=Thread(target=connect_db) t2=Thread(target=check_web) t1.start() t2.start()
但是如果只是偶尔的中断,那么有可能误判,所以需要设置一个重复连接3次
import time import random from threading import Thread,Event def connect_db(): count=0 while count<3: e.wait(0.5)#这里再等待check_web线程的信号,只等待0.5秒,缩短时间,增加真实性 if e.is_set()==True: print('连接数据库') break else: count += 1 print('第%s次连接失败'%count) else: raise TimeoutError('数据库连接超时') def check_web(): time.sleep(random.randint(0,2)) e.set() e=Event() t1=Thread(target=connect_db) t2=Thread(target=check_web) t1.start() t2.start()
条件condition
from threading import Thread,Condition def func(i): con.acquire() con.wait()#等钥匙 print('在第%s个循环里'%i) con.release() con=Condition() for i in range(10): t=Thread(target=func,args=(i,)) t.start() while 1: num=int(input('>>>')) con.acquire() con.notify(num)#这里是造钥匙的过程 con.release()
注意这里的钥匙是一次性的,和锁那里的钥匙有区别,也就是说进程使用完这把钥匙后这把钥匙就作废了,不会还回去。条件在实际中应用的地方不太多
定时器
from threading import Timer def func(): print('时间同步') t=Timer(2,func).start()#2秒后执行线程func
新的需求:每隔5秒执行一次线程
from threading import Timer import time def func(): print('时间同步') while 1: t=Timer(2,func).start() time.sleep(5)
队列
线程执行的速度很快,同样这个线程执行的结果还没放回去,下一个线程开始执行了,容易造成数据的不安全,所以队列就显得很重要。
具体示例参考进程。队列的特点是先进先出;但是栈却是先进后出
栈的例子:
import queue q=queue.LifoQueue()#栈,先进后出 q.put(1) q.put(2) q.put(3) print(q.get())
优先级队列
import queue q=queue.PriorityQueue()#优先级队列 q.put((20,'a'))#前面的数字是顺序序号 q.put((30,'b')) q.put((10,'c'))#结果是c,因为10的序号在最前面 print(q.get())
进阶
import queue q=queue.PriorityQueue()#优先级队列 q.put((20,'a'))#前面的数字是顺序序号 q.put((1,'b')) q.put((1,'c'))#序号一样,按照后面字母的ASCII码排列 print(q.get())
线程池
from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(1) print(n) tpool=ThreadPoolExecutor(max_workers=5)#max_workers是线程数,一般不要超过CPU核数的5倍 for i in range(20): tpool.submit(func,i)#结果是异步执行的 print('主线程')
进阶
from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(1) print(n) tpool=ThreadPoolExecutor(max_workers=5)#max_workers是线程数,一般不要超过CPU核数的5倍 for i in range(20): tpool.submit(func,i)#结果是异步执行的 tpool.shutdown()#执行后就不执行print('主线程')了,相当于close+join的叠加 print('主线程')
进阶
from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(1) print(n)#注意这里不一定是按照结果来显示 return n*n tpool=ThreadPoolExecutor(max_workers=5)#max_workers是线程数,一般不要超过CPU核数的5倍 t_list=[] for i in range(20): t=tpool.submit(func,i)#结果是异步执行的 t_list.append(t) tpool.shutdown()#执行后就不执行print('主线程')了 print('主线程') for t in t_list: print(t.result())#但是这里因为t的顺序是来自于for循环,所以这里一定是按照顺序来执行的
利用map,可以得到类似的结果
from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(1) print(n)#注意这里不一定是按照结果来显示 return n*n tpool=ThreadPoolExecutor(max_workers=5)#max_workers是线程数,一般不要超过CPU核数的5倍 tpool.map(func,range(20))
回调函数
from concurrent.futures import ThreadPoolExecutor import time def func(n): time.sleep(1) print(n)#注意这里不一定是按照结果来显示 return n*n #回调函数 def call_back(m): print('结果是%s'%m.result())#这里的m是一个对象,如果需要得到结果需要用result方法 tpool=ThreadPoolExecutor(max_workers=5) for i in range(20): tpool.submit(func,i).add_done_callback(call_back)