练习:
想与多个用户进行通讯,且支持并发
实现从单线程,无法并发变成多线程,支持并发
import socket from threading import Thread server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn): while True: try: data = conn.recv(1024) if not len(data): break conn.send(data.upper()) except ConnectionResetError as a: print(a) break conn.close() while True: conn,addr = server.accept() # talk(conn) t = Thread(target=talk,args=(conn,)) t.start()
""" 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本质也是一把互斥锁:将并发变成串行牺牲效率保证数据的安全 用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发) python的多线程没法利用多核优势 是不是就是没有用了? GIL的存在是因为CPython解释器的内存管理不是线程安全的
1.引用计数
引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型赋值给该变量时,则这个值的引用次数就是1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数就减1。当这个引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其所占的内存空间给收回来。这样,垃圾收集器下次再运行时,它就会释放那些引用次数为0的值所占的内存。
2.标记清除
圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。然后,它会去掉环境中的变量以及被环境中的变量引用的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后。垃圾收集器完成内存清除工作,销毁那些带标记的值,并回收他们所占用的内存空间.
3.分代回收
1.刚产生的变量值放在新生代中高频率检查,如果引用计数为0,就是采用引用计数机制回收,长期存活的变量值经过多次检查后会提高分代 2.分带越高,检查频率越低,且还能继续提高一直存活的变量值的分带,从而来提高整体垃圾回收的效率
加锁的时机:在调用解释器时立即加锁
解锁时机:
-
当前线程遇到了IO时释放
-
当前线程执行时间超过设定值时释放
GIL的优点:
-
保证了CPython中的内存管理是线程安全的
GIL的缺点:
-
互斥锁的特性使得多线程无法并行
### 问题 GIL是python的特点吗? 不是,它是CPython解释器的特点,仅在CPython中存在. 单进程下多个线程无法利用多核优势,是所有解释性语言的通病. 针对不同的数据应该加不同的锁进行处理
四个任务 计算密集型的 10s 单核情况下 开线程更省资源 多核情况下 开进程 10s 开线程 40s 四个任务 IO密集型的 单核情况下 开线程更节省资源 多核情况下 开线程更节省资源
from multiprocessing import Process from threading import Thread import os , time # 计算密集型 def work(): res = 0 for i in range(1000): res*=i if __name__ == '__main__': l = [] print(os.cpu_count()) start = time.time() for i in range(6): # p = Process(target=work) #创建进程 p = Thread(target=work) # 创建线程 l.append(p) p.start() for p in l: p.join() stop = time.time() print('run time is %s'%(stop-start))
from multiprocessing import Process from threading import Thread import os , time # io密集型 def work(): time.sleep(1) if __name__ == '__main__': l = [] print(os.cpu_count()) start = time.time() for i in range(400): p = Process(target=work) l.append(p) p.start() for p in l: # print(p) p.join() stop = time.time() print('run time is %s'%(stop-start))
死锁
死锁问题 当程序出现了不止一把锁,分别被不同的线程持有, 有一个资源 要想使用必须同时具备两把锁 这时候程序就会进程无限卡死状态 ,这就称之为死锁
from threading import Thread,Lock mutex1 = Lock() mutex2 = Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutex1.acquire() print('%s 抢到了锁1'%self.name) mutex2.acquire() print('%s 抢到了锁2' % self.name) mutex1.release() print('%s 释放了锁1'%self.name) mutex2.release() print('%s 释放了锁2' % self.name) def func2(self): mutex2.acquire() print('%s 抢到了锁2'%self.name) mutex1.acquire() print('%s 抢到了锁1' % self.name) mutex2.release() print('%s 释放了锁2'%self.name) mutex1.release() print('%s 释放了锁1' % self.name) for i in range(10): t = MyThread() t.start()
可重入锁
Rlock 称之为递归锁或者可重入锁
Rlock不是用来解决死锁问题的
与Lock唯一的区别: Rlock同一线程可以多次执行acquire 但是执行几次acquire就应该对应release几次 如果一个线程已经执行过acquire 其他线程将无法执行acquire
from threading import Thread,RLock """ Rlock可以被第一个抢到锁的人连续的acquire和release 每acquire一次锁身上的计数加1 每release一次锁身上的计数减1 只要锁的计数不为0 其他人都不能抢 """ mutex1 = mutex2 = RLock() class MyThread(Thread): def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 self.func1() self.func2() def func1(self): mutex1.acquire() print('%s 抢到了锁1'%self.name)# self.name等价于current_thread().name mutex2.acquire() print('%s 抢到了锁2' % self.name) mutex1.release() print('%s 释放了锁1'%self.name) mutex2.release() print('%s 释放了锁2' % self.name) def func2(self): mutex2.acquire() print('%s 抢到了锁2'%self.name) mutex1.acquire() print('%s 抢到了锁1' % self.name) mutex2.release() print('%s 释放了锁2'%self.name) mutex1.release() print('%s 释放了锁1' % self.name) for i in range(10): t = MyThread() t.start() ''' 只要类加括号实例化对象 无论传入的参数是否一样生成的对象肯定不一样 单例模式除外 '''
信号量可能在不同的领域中 对应不同的知识点 可以现在被锁定的代码 同时可以被多少线程并发访问 Lock 锁住一个马桶 同时只能有一个 Semaphore 锁住一个公共厕所 同时可以来一堆人 用途: 仅用于控制并发访问 并不能防止并发修改造成的问题
from threading import Thread,Semaphore import time import random a = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 def task(name): a.acquire() print('%s 在上厕所'%name) time.sleep(random.randint(1,3)) a.release() for i in range(10): t = Thread(target=task,args=(i,)) t.start()
Event事件 事件表示在某个时间发生了某个事情的通知信号,用于线程间协同工作。 因为不同线程之间是独立运行的状态不可预测, 所以一个线程与另一个线程间的数据是不同步的 当一个线程需要利用另一个线程的状态来确定自己的下一步操作时, 就必须保持线程间数据的同步,Event就可以实现线程间同步
from threading import Thread,Event import time # 生成一个event对象 e = Event() def light(): print('红灯亮') time.sleep(1) e.set() # 发送信号 print('绿灯亮') def car(name): print('%s 正在等红灯'% name) e.wait() #等待信号 print('%s 加油门,踩离合'% name) t = Thread(target=light) t.start() for i in range(10): t = Thread(target=car,args=('萝卜%s'%i,)) t.start()
event.isSet():返回event的状态值; event.wait():将阻塞线程;知道event的状态为True event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度; event.clear():恢复event的状态值为False。
同一个进程下的多个线程本来就是数据共享 为什么还要用队列??? 因为队列是管道+锁 使用队列你就不需要自己手动操作锁的问题 因为锁操作的不好极容易产生死锁现象
1.Queue 先进先出队列
与多进程中的Queue使用方式完全相同,区别仅仅是不能被多进程共享。
import queue q = queue.Queue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get())
2.LifoQueue 后进先出队列
import queue q = queue.LifoQueue() q.put(1) q.put(2) q.put(3) print(q.get()) print(q.get())
3.PriorityQueue 优先级队列
该队列可以为每个元素指定一个优先级,这个优先级可以是数字,字符串或其他类型,但是必须是可以比较大小的类型,取出数据时会按照从小到大的顺序取出
import queue q = queue.PriorityQueue() q.put((10,'hehe')) q.put((20,'enen')) q.put((-10,'wenwen')) # 取值,得到的是个元组,可以根据索引取值 print(q.get()[1]) print(q.get()) print(q.get()) print(q.get())