理论部分
一、什么是线程:
1.线程:一条流水线的工作过程
2.一个进程里至少有一个线程,这个线程叫主线程
进程里真正干活的就是线程
3.进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
4.多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。
共享就存在竞争,----加锁-----队列
1 from multiprocessing import Process 2 from threading import Thread 3 import os 4 import time 5 n=100 6 def work(): 7 global n 8 n-=100 9 10 if __name__ == '__main__': 11 p=Thread(target=work,) 12 p.start() 13 p.join() 14 print('主',n) 15 16 #结果是0
二、线程的创建开销小,因而创建线程的速度快
1 from multiprocessing import Process 2 from threading import Thread 3 import os 4 import time 5 def work(): 6 print('<%s> is running' %os.getpid()) 7 time.sleep(2) 8 print('<%s> is done' %os.getpid()) 9 10 if __name__ == '__main__': 11 t=Thread(target=work,) 12 t.start() 13 print('主',os.getpid()) 14 15 16 结果: 17 <20348> is running 18 主 20348 19 <20348> is done
1.进程之间内存空间是互相隔离的
三、线程与进程的区别
1.线程共享创建它的进程的地址空间;进程有自己的地址空间。
2.线程可以直接访问其进程的数据段;进程有自己的父进程数据段的副本。(Linux系统,Windows系统也可以这么说,但是要注意Windows没有子进程的概念)
3.线程可以与进程的其他线程直接通信;进程必须使用进程间通信来与同级进程通信。
4.新线程很容易创建;新进程需要父进程的重复。
总之记住两点就ok: 1、多线程共享它们进程的资源,2、线程的创建开销小
1 from multiprocessing import Process 2 from threading import Thread 3 import os 4 import time 5 n=100 6 def work(): 7 global n 8 n-=100 9 10 if __name__ == '__main__': 11 # p=Process(target=work,) 12 p=Thread(target=work,) 13 p.start() 14 p.join() 15 print('主',n)
5.扩展:分布式与集中式(所有的东西放在一起)
四、为什么实用多线程:
1、多线程共享它们进程的资源,2、线程的创建开销小
五、多线程的应用举例
开启一个字处理软件进程,该进程肯定需要办不止一件事情,比如监听键盘输入,处理文字,定时自动将文字保存到硬盘,这三个任务操作的都是同一块数据,因而不能用多进程。只能在一个进程里并发地开启三个线程,如果是单线程,那就只能是,键盘输入时,不能处理文字和自动保存,自动保存时又不能输入和处理文字。
代码部分
六、开启进程的两种方式:
1 #方式一 2 from threading import Thread 3 import time 4 def sayhi(name): 5 time.sleep(2) 6 print('%s say hello' %name) 7 8 if __name__ == '__main__': 9 t=Thread(target=sayhi,args=('egon',)) 10 t.start() 11 print('主线程')
1 #方式二 2 from threading import Thread 3 import time 4 class Sayhi(Thread): 5 def __init__(self,name): 6 super().__init__() 7 self.name=name 8 def run(self): 9 time.sleep(2) 10 print('%s say hello' % self.name) 11 12 13 if __name__ == '__main__': 14 t = Sayhi('egon') 15 t.start() 16 print('主线程')
七、多线程共享同一个进程内的地址空间
多线程共享——>竞争——>锁
八、练习
from socket import * from threading import Thread s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('192.168.20.95',8082)) s.listen(5) def talk(conn): while True: try: cmd=conn.recv(1024) if not cmd:break conn.send(cmd.upper()) except Exception: break conn.close() if __name__ == '__main__': while True: conn,addr=s.accept() p=Thread(target=talk,args=(conn,)) p.start() s.close()
1 from socket import * 2 c=socket(AF_INET,SOCK_STREAM) 3 c.connect(('192.168.20.95',8082)) 4 5 while True: 6 msg=input('>>: ').strip() 7 if not msg:continue 8 c.send(msg.encode('utf-8')) 9 data=c.recv(1024) 10 print(data.decode('utf-8')) 11 c.close()
练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
1 from threading import Thread 2 input_l=[] 3 format_l=[] 4 def talk(): 5 while True: 6 msg=input('>>: ') 7 if not msg:continue 8 input_l.append(msg) 9 def format(): 10 while True: 11 if input_l: 12 res=input_l.pop() 13 format_l.append(res.upper()) 14 def save(): 15 with open('db.txt','a') as f: 16 while True: 17 if format_l: 18 f.write('%s ' %(format_l.pop())) 19 f.flush() 20 21 22 if __name__ == '__main__': 23 t1=Thread(target=talk) 24 t2=Thread(target=format) 25 t3=Thread(target=save) 26 27 t1.start() 28 t2.start() 29 t3.start()
九、线程相关的其他属性及方法(了解)
1 Thread实例对象的方法 2 # isAlive(): 返回线程是否活动的。 3 # getName(): 返回线程名。 4 # setName(): 设置线程名。 5 6 threading模块提供的一些方法: 7 # threading.currentThread(): 返回当前的线程变量。 8 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 9 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
1 from threading import Thread,currentThread,activeCount 2 import os,time,threading 3 def talk(): 4 print('%s is running' %currentThread().getName()) 5 6 if __name__ == '__main__': 7 # t=Thread(target=talk,name='egon') 8 t=Thread(target=talk) 9 t.start() 10 print(t.name) 11 print(t.getName()) 12 print(t.is_alive()) 13 print(currentThread().getName()) 14 print(threading.enumerate()) 15 print('主',activeCount()) 16 17 18 19 #结果 20 Thread-1 is running 21 Thread-1 22 Thread-1 23 False 24 MainThread 25 [<_MainThread(MainThread, started 33752)>] 26 主 1
十、守护进程
进程: 主进程代码完——>守护死
线程: 主线程的非守护线程完——>守护死
十一、全局解释器锁:(GIL——CPython解释器的)
1.保护系统级别的(CPython解释器)代码的安全,但是不能保护用户级别的数据(软件产生的代码)安全
2.抢锁: 所有线程抢的是GIL锁,或者说所有线程抢的是执行权限
3.释放锁: ①I/O阻塞 ②运行时间过长
4.在一个进程中多个线程通过GIL就会变成串行的效果
5.有了GIL的存在,同一时刻同一进程中只有一个线程被执行
6.应用:
多线程用于IO密集型,如socket,爬虫,web
多进程用于计算密集型,如金融分析
十二、同步锁:
GIL VS Lock
过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限
线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果
既然是串行,那我们执行
t1.start()
t1.join
t2.start()
t2.join()
这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。
1 分析:
2 #1.100个线程去抢GIL锁,即抢执行权限
3 #2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
4 #3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
5 #4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
练习:
1 from threading import Thread,Lock 2 import time 3 n=100 4 def work(): 5 mutex.acquire() 6 global n 7 temp=n 8 time.sleep(0.1) 9 n=temp-1 10 mutex.release() 11 12 if __name__ == '__main__': 13 mutex=Lock() 14 t_l=[] 15 for i in range(100): 16 t=Thread(target=work) 17 t_l.append(t) 18 t.start() 19 for t in t_l: 20 t.join() 21 print(n)