并发编程 线程
1.线程的概念:
1. 什么是线程: 进程和线程都是虚拟单位,都是用来帮助我木门形象的描述某种事物
进程 : 资源单位 线程:执行单位
每一个进程都自带一个线程,线程才是真正的执行单位,进程只是在线程运行过程中提供代码运行所需要的资源
2. 为什么要有线程:
开进程 1 申请内存空间 耗资源, 拷贝代码, 耗资源
开线程 一个进程内可以起多个线程,并且线程与线程之间的数据是共享的
开启线程的开销要远远小于开启进程的开销
2.Threading.Thread 创建线程模块
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
3.代码的实现
1.创建线程的两种方式
from threading import Thread import time def func(name): print(f'{name}线程正在创建') time.sleep(2) print(f'{name}线程结束') # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 if __name__ == '__main__': t = Thread(target=func,args=('小明',)) t.start() print('主')
from threading import Thread import time class MyThread(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print(f'{self.name}线程正在创建') time.sleep(2) print(f'{self.name}线程结束') if __name__ == '__main__': t = MyThread('小明') t.start() print('主')
2.线程对象及其他方法:
from threading import Thread,current_thread,active_count import time import os def task(name,i): print('%s is running'%name) # print('子current_thread:',current_thread().name) # print('子',os.getpid()) time.sleep(i) print('%s is over'%name) # 开线程不需要在__main__代码块内 但是习惯性的还是写在__main__代码块内 t = Thread(target=task,args=('egon',1)) t1 = Thread(target=task,args=('jason',2)) t.start() # 告诉操作系统开辟一个线程 线程的开销远远小于进程 t1.start() # 告诉操作系统开辟一个线 t1.join() # 主线程等待子线程运行完毕 print('当前正在活跃的线程数',active_count()) # 小的代码执行完 线程就已经开启了 print('主') # print('主current_thread:',current_thread().name) # 线程名字 # print('主',os.getpid())
3. 守护线程:与进程方法相同,但需要注意的是:主线程的结束也就以为着进程的结束,主线程必须等待其他非守护线程的结束才能结束,也就是意味着子线程在运行的时候需要使用进程中的资源,而主线程一旦结束了资源也就销毁了
from threading import Thread,current_thread import time def task(i): print(current_thread().name) time.sleep(i) print('GG') t = Thread(target=task,args=(1,)) t.daemon = True t.start() print('主') ''' Thread-1 主 因为t线程为守护进程,主线程运行结束t线程也跟着结束 '''
4. 同一进程中线程之间的数据是共享的
from threading import Thread money = 666 def task(): global money money = 999 t = Thread(target=task) t.start() t.join() print(money) # 999 同一进程中线程间的数据是共享的
5. 线程中的互斥锁与进程中的使用方法一致
from threading import Thread,Lock import time n = 100 def task(mutex): global n mutex.acquire() tmp = n time.sleep(0.1) n = tmp - 1 mutex.release() t_list = [] mutex = Lock() for i in range(100): t = Thread(target=task,args=(mutex,)) # 创建100个线程 t.start() t_list.append(t) # 将线程对象添加到列表中 for t in t_list: # 遍历线程对象列表 t.join() # 等待所有线程程运行结束再执行主线程 print(n) # 0
6. 用线程实现sockte服务端的并发:
服务端:
import socket from threading import Thread def talk(conn): while 1: try: ret = conn.recv(1024) if len(ret) == 0: break print(ret) conn.send(ret.upper()) except ConnectionResetError: break conn.close() sk = socket.socket() sk.bind(('127.0.0.1',8080)) sk.listen() while 1: conn,addr = sk.accept() t = Thread(target=talk,args=(conn,)) t.start()
客户端:
import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) while 1: sk.send(b'hello') ret = sk.recv(1024) print(ret)
7: GIL全局解释器锁:
""" In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. """
ps:python解释器有很多种 最常见的就是Cpython解释器
GIL本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全 他是用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)GIL的存在是因为CPython解释器的内存管理不是线程安全的,也就是说同一个进程中的多个线程再同一时间只有一个线程被cpu执行,GIL全局解释器锁只是针对线程的,进入IO自动释放
总结.在执行多个计算密集型的任务时.在计算机单核的情况下开线程最好,在多核情况下开进程会效率更高
.在执行多个IO密集型的任务时.开线程时最好的
8:死锁与递归锁:当一个进程或线程中同时出现多把锁容易造成死锁显现,程序卡死
递归锁: Rlock,可以多次acquire与release 当一个线程或进程中所有acquire都被release,其他的线程才能获得资源,注意使用递归锁时是链式赋值
科学家吃面示例:
import time from threading import Thread,RLock lock1 = lock2 = RLock() def eat1(name): lock2.acquire() print('%s 抢到了面条'%name) lock1.acquire() print('%s 抢到了叉子'%name) print('%s 吃面'%name) lock1.release() lock2.release() def eat2(name): lock1.acquire() print('%s 抢到了叉子' % name) time.sleep(1) lock2.acquire() print('%s 抢到了面条' % name) print('%s 吃面' % name) lock2.release() lock1.release() for name in ['小明','小黄','小亮']: t1 = Thread(target=eat1,args=(name,)) t2 = Thread(target=eat2,args=(name,)) t1.start() t2.start()
9 信号量: Semaphore: 用锁的原理实现的,内置了一个计数器,在同一时间只能有指定数量的进程或线程执行某一段代码,如果把互斥锁比作一个车位的话,那么信号量相当于多个车位
from threading import Thread,Semaphore # 线程使用信号量导入方法 # from multiprocessing import Process,Semaphore # 进程使用信号量导入方法 import time import random sm = Semaphore(5) # 5个车位 def func(name): sm.acquire() # 占车位 print(f'{name}占了一个车位') time.sleep(random.randint(1,2)) sm.release() # 让出车位 print(f'{name}让了一个车位') for i in range(20): t = Thread(target=func,args=(i,)) t.start() '''同时占用车位最多不会超过5个,一个让出去另一个才会占用 '''
10:事件:event类似于join 不过事件是在处理两个子进程或子线程之间互相等待的过程
常用方法:
from multiprocessing import Event e = Event() e.wait() # 阻塞态,状态为True不堵塞,为False 堵塞 e.is_set() # 查看状态 e.set() # False ---> True e.clear() # True ---> False
红绿灯示例:
from threading import Event,Thread import time e = Event() def light(): print('红灯亮了') time.sleep(3) e.set() print('绿灯亮了') def car(name): print(f'{name}正在等红灯') e.wait() print(f'{name}可以行驶了') l = Thread(target=light) l.start() for i in range(10): c = Thread(target=car,args=(i,)) c.start()
11. 线程队列:
1: queue 队列
import queue q = queue.Queue() q.put(1) q.put(2) print(q.get()) # 1 print(q.get()) # 2
2: Lifoqueue 堆栈
import queue q = queue.LifoQueue() # 堆栈 q.put(1) q.put(2) print(q.get()) # 2 print(q.get()) # 1
3: PriorityQueue 优先级队列
import queue q = queue.PriorityQueue() # 堆栈 q.put((1,'a')) # 放一个元组,第一个元素是优先级为int型,数字越小优先级越高 q.put((0,'b')) q.put((-1,'c')) q.put((1,'d')) # 同一优先级,按照内容对应的ASCII码表来确认优先级 print(q.get()) # (-1, 'c') print(q.get()) # (0, 'b') print(q.get()) # (1, 'a') print(q.get()) # (1, 'd')