线程(from threading import Thread):CPU调度的最小单位
线程的两种创建方式:
方式一:
1 from threading import Thread
2 def f1(i):
3 print(i)
4 if __name__ == '__main__':
5 for i in range(10):
6 t = Thread(target=f1,args=(i,))
7 t.start()
8 print('主线程')
方式二:
1 from threading import Thread
2 class MyThread(Thread):
3 def __init__(self, i):
4 super().__init__()
5 self.i = i
6
7 def run(self):
8 print('%s哈哈' % self.i)
9
10 if __name__ == '__main__':
11 for i in range(10):
12 t = MyThread(i)
13 t.start()
14 print('主线程')
线程之间数据共享的验证:
1 import time
2 from threading import Thread
3 num = 100
4 def f1():
5 global num
6 num = 33
7 time.sleep(2)
8 print('num>>>',num)
9 if __name__ == '__main__':
10 t = Thread(target=f1,)
11 t.start()
12 print('主线程的num',num)
多线程跟多进程的效率对比:
结论:当遇到io阻塞情况,多线程比多进程效率高,当遇到大量计算情况,多进程比多线程效率高
1 import time
2 from threading import Thread
3 from multiprocessing import Process
4 def f1():
5 # time.sleep(1)
6 n = 10
7 for i in range(10000000):
8 n += i
9 if __name__ == '__main__':
10 t_s_time = time.time()
11 t_list = []
12 for i in range(10):
13 t = Thread(target=f1,)
14 t.start()
15 t_list.append(t)
16 [tt.join() for tt in t_list]
17 t_e_time = time.time()
18 t_dif_time = t_e_time - t_s_time
19
20 p_s_time = time.time()
21 p_list = []
22 for i in range(10):
23 p = Process(target=f1,)
24 p.start()
25 p_list.append(p)
26 [pp.join() for pp in p_list]
27 p_e_time = time.time()
28 p_dif_time = p_e_time - p_s_time
29 print('线程的时间:',t_dif_time)
30 print('进程的时间:',p_dif_time)
线程锁(互斥锁):
作用:牺牲了效率,保证了数据安全
1 import time
2 from threading import Thread,Lock
3 num = 100
4 def f1(t_lock):
5 t_lock.acquire()
6 global num
7 tme = num
8 tme -= 1
9 num = tme
10 time.sleep(1)
11 print('子线程的num',num)
12 t_lock.release()
13 if __name__ == '__main__':
14 t_lock = Lock()
15 t_list = []
16 for i in range(10):
17 t = Thread(target=f1,args=(t_lock,))
18 t.start()
19 t_list.append(t)
20 [tt.join() for tt in t_list]
21 print('主线程的num',num)
死锁现象:出现在锁嵌套的时候,双方互相抢对方已经拿到的锁,导致双方互相等待,程序卡住(重点)
死锁模拟:
1 import time
2 from threading import Thread,Lock
3 def f1(t_lockA,t_lockB):
4 t_lockA.acquire()
5 print('f1抢到了A锁')
6 time.sleep(1)
7 t_lockB.acquire()
8 print('f1抢到了B锁')
9 t_lockB.release()
10 t_lockA.release()
11 def f2(t_lockA,t_lockB):
12 t_lockB.acquire()
13 print('f2抢到了B锁')
14 time.sleep(1)
15 t_lockA.acquire()
16 print('f2抢到了A锁')
17 t_lockA.release()
18 t_lockB.release()
19 if __name__ == '__main__':
20 t_lockA = Lock()
21 t_lockB = Lock()
22 t1 = Thread(target=f1,args=(t_lockA,t_lockB))
23 t2 = Thread(target=f2,args=(t_lockA,t_lockB))
24 t1.start()
25 t2.start()
递归锁:最常用的线程锁,解决了死锁现象
Rlock 首先本身就是个互斥锁,维护了一个计数器,每次acquire就+1,release就-1,当计数器为0的时候,大家才能抢这个锁
1 import time
2 from threading import Thread,RLock
3 def f1(lokA,lokB):
4 lokA.acquire()
5 print('f1抢到了A锁')
6 time.sleep(1)
7 lokB.acquire()
8 print('f1抢到B锁了')
9 lokB.release()
10 lokA.release()
11 def f2(lokA,lokB):
12 lokB.acquire()
13 print('f2抢到了B锁')
14 time.sleep(1)
15 lokA.acquire()
16 print('f2抢到了A锁')
17 lokA.release()
18 lokB.release()
19 if __name__ == '__main__':
20 lokA = lokB = RLock() #递归锁,维护一个计数器,acquire一次就加1,release就减1
21 t1 = Thread(target=f1,args=(lokA,lokB))
22 t2 = Thread(target=f2,args=(lokA,lokB))
23 t1.start()
24 t2.start()
多线程守护跟多进程守护的区别:
区别:
守护进程:主进程代码执行运行结束,守护进程随之结束
守护线程:守护线程会等待所有非守护线程运行结束才结束
代码示例:
1 import time
2 from threading import Thread
3 from multiprocessing import Process
4 def f1():
5 time.sleep(2)
6 print('一号')
7 def f2():
8 time.sleep(3)
9 print('二号')
10 if __name__ == '__main__':
11 t1 = Thread(target=f1,)
12 t2 = Thread(target=f2,)
13 t1.daemon = True
14 t2.daemon = True #守护线程:守护线程会等待所有非守护线程运行结束才结束
15 t1.start()
16 t2.start()
17 print('线程结束')
18 p1 = Process(target=f1,)
19 p2 = Process(target=f2,)
20 p1.daemon = True
21 p2.daemon = True #守护进程:主进程代码执行运行结束,守护进程随之结束
22 p1.start()
23 p2.start()
24 print('进程结束')
GIL锁 : cpython解释器上的一把互斥锁
首先需要明确的一点是GIL
并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成
可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。
像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL
归结为Python语言的缺陷。
所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL
GIL本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。
要想了解GIL,首先确定一点:每次执行python程序,都会产生一个独立的进程。例如python test.py,python aaa.py,python bbb.py会产生3个不同的python进程
1 '''
2 #验证python test.py只会产生一个进程
3 #test.py内容
4 import os,time
5 print(os.getpid())
6 time.sleep(1000)
7 '''
8 python3 test.py
9 #在windows下
10 tasklist |findstr python
11 #在linux下
12 ps aux |grep python
如果多个线程的target=work,那么执行流程是
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
GIL保护的是解释器级的数据,保护用户自己的数据则需要自己加锁处理,如下图
结论:
对计算来说,cpu越多越好,但是对于I/O来说,再多的cpu也没用
当然对运行一个程序来说,随着cpu的增多执行效率肯定会有所提高(不管提高幅度多大,总会有所提高),这是因为一个程序基本上不会是纯计算或者纯I/O,所以我们只能相对的去看一个程序到底是计算密集型还是I/O密集型,从而进一步分析python的多线程到底有无用武之地
#分析:
我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:
方案一:开启四个进程
方案二:一个进程下,开启四个线程
#单核情况下,分析结果:
如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜
如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜
#多核情况下,分析结果:
如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜
如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜
#结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。
计算密集型:多进程效率高
1 from multiprocessing import Process
2 from threading import Thread
3 import os,time
4 def work():
5 res=0
6 for i in range(100000000):
7 res*=i
8
9
10 if __name__ == '__main__':
11 l=[]
12 print(os.cpu_count()) #本机为4核
13 start=time.time()
14 for i in range(4):
15 p=Process(target=work) #耗时5s多
16 p=Thread(target=work) #耗时18s多
17 l.append(p)
18 p.start()
19 for p in l:
20 p.join()
21 stop=time.time()
22 print('run time is %s' %(stop-start))
I/O密集型:多线程效率高
1 from multiprocessing import Process
2 from threading import Thread
3 import threading
4 import os,time
5 def work():
6 time.sleep(2)
7 print('===>')
8
9 if __name__ == '__main__':
10 l=[]
11 print(os.cpu_count()) #本机为4核
12 start=time.time()
13 for i in range(400):
14 # p=Process(target=work) #耗时12s多,大部分时间耗费在创建进程上
15 p=Thread(target=work) #耗时2s多
16 l.append(p)
17 p.start()
18 for p in l:
19 p.join()
20 stop=time.time()
21 print('run time is %s' %(stop-start))
应用:
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
写一个简易socket多人聊天:
1 import socket 2 from threading import Thread 3 class MySocket: 4 def __init__(self,server_addr): 5 self.server_addr = server_addr 6 self.socket = socket.socket() 7 def serve_forever(self): 8 self.socket.bind(self.server_addr) 9 self.socket.listen(5) 10 self.build_connect() 11 def build_connect(self): #建立连接 12 while 1: 13 conn,addr = self.socket.accept() 14 t = Thread(target=self.handle,args=(conn,)) 15 t.start() 16 def handle(self,conn): 17 while 1: 18 from_client_msg = conn.recv(1024).decode('utf-8') 19 print('来自客户的消息:',from_client_msg) 20 to_client_msg = input('服务端说:') 21 conn.send(to_client_msg.encode('utf-8')) 22 if __name__ == '__main__': 23 ip_port = ('127.0.0.1',8013) 24 server = MySocket(ip_port) 25 server.serve_forever()
1 import socket 2 client = socket.socket() 3 client.connect(('127.0.0.1',8013)) 4 while 1: 5 to_server_msg = input('客户端说:') 6 client.send(to_server_msg.encode('utf-8')) 7 from_server_msg = client.recv(1024).decode('utf-8') 8 print('来自服务端的消息:',from_server_msg)