一 线程理论
什么是线程
""" 进程:资源单位 线程:执行单位 将操作系统比喻成一个大的工厂 那么进程就相当于工厂里面的车间 而线程就是车间里面的流水线 每一个进程肯定自带一个线程 再次总结: 进程:资源单位(起一个进程仅仅只是在内存空间中开辟一块独立的空间) 线程:执行单位(真正被cpu执行的其实是进程里面的线程,线程指的就是代码的执行过程,执行代码中所需要使用到的资源都找所在的进程索要) 进程和线程都是虚拟单位,只是为了我们更加方便的描述问题 """
为何要有线程
""" 开设进程 1.申请内存空间 耗资源 2.“拷贝代码” 耗资源 开线程 一个进程内可以开设多个线程,在用一个进程内开设多个线程无需再次申请内存空间操作 总结: 开设线程的开销要远远的小于进程的开销 同一个进程下的多个线程数据是共享的!!! """ 我们要开发一款文本编辑器 获取用户输入的功能 实时展示到屏幕的功能 自动保存到硬盘的功能 针对上面这三个功能,开设进程还是线程合适??? 开三个线程处理上面的三个功能更加的合理
二 开启线程的两种方式
# 方案一 from threading import Thread import time def dask(name): print('%s is run'%name) time.sleep(1) print('%s is end'%name) # 开启线程不需要在main下面执行代码 直接书写就可以 # 但是我们还是习惯性的将启动命令写在main下面 # 创建线程的开销非常小 几乎是代码一执行线程就已经创建了 if __name__ == '__main__': t = Thread(target=dask,args=('xxx',)) t.start() print('主')
# 方案二 from threading import Thread import time class Mythead(Thread): def __init__(self,name): super().__init__() self.name = name def run(self): print('%s is run'%self.name) time.sleep(1) print('%s is end'%self.name) if __name__ == '__main__': t = Mythead('xxxx') t.start() print('主')
三 TCP服务端实现并发的效果
服务端
from socket import * from threading import Thread server = socket(AF_INET,SOCK_STREAM) server.bind(('127.0.0.1',8080)) server.listen(5) def dask(conn): while True: try: date = conn.recv(1024) if len(date) == 0: break print(date.decode('utf-8')) conn.send(date.upper()) except Exception as f: print(f) break conn.close() if __name__ == '__main__': while True: conn,client_addrs = server.accept() t = Thread(target=dask,args=(conn,)) t.start()
客户端
from socket import * client = socket() client.connect(('127.0.0.1',8080)) while True: client.send(b'hello world') data = client.recv(1024) print(data.decode('utf-8'))
四 线程对象的join方法
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(1) print('%s is over'%name) if __name__ == '__main__': t = Thread(target=task,args=('egon',)) t.start() t.join() # 主线程等待子线程运行结束再执行 print('主')
五 同一个进程下的多个线程数据是共享的
from threading import Thread import time money = 100 def task(): global money money = 666 print(money) if __name__ == '__main__': t = Thread(target=task) t.start() t.join() print(money)
六 线程对象属性及其他方法
from threading import Thread, active_count, current_thread import os,time def task(n): # print('hello world',os.getpid()) print('hello world',current_thread().name) time.sleep(n) if __name__ == '__main__': t = Thread(target=task,args=(1,)) t1 = Thread(target=task,args=(2,)) t.start() t1.start() t.join() print('主',active_count()) # 统计当前正在活跃的线程数 # print('主',os.getpid()) # print('主',current_thread().name) # 获取线程名字
七 守护线程
# from threading import Thread # import time # # # def task(name): # print('%s is running'%name) # time.sleep(1) # print('%s is over'%name) # # # if __name__ == '__main__': # t = Thread(target=task,args=('egon',)) # t.daemon = True # t.start() # print('主') """ 主线程运行结束之后不会立刻结束 会等待所有其他非守护线程结束才会结束 因为主线程的结束意味着所在的进程的结束 """ # 稍微有一点迷惑性的例子 from threading import Thread import time def foo(): print(123) time.sleep(1) print('end123') def func(): print(456) time.sleep(3) print('end456') if __name__ == '__main__': t1 = Thread(target=foo) t2 = Thread(target=func) t1.daemon = True t1.start() t2.start() print('主.......')
八 线程互斥锁
from threading import Thread,Lock import time money = 100 mutex = Lock() def task(): global money mutex.acquire() tmp = money time.sleep(0.1) money = tmp - 1 mutex.release() if __name__ == '__main__': t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(money)
九 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. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.) """ """ python解释器其实有多个版本 Cpython Jpython Pypypython 但是普遍使用的都是CPython解释器 在CPython解释器中GIL是一把互斥锁,用来阻止同一个进程下的多个线程的同时执行 同一个进程下的多个线程无法利用多核优势!!! 疑问:python的多线程是不是一点用都没有???无法利用多核优势 因为cpython中的内存管理不是线程安全的 内存管理(垃圾回收机制) 1.应用计数 2.标记清楚 3.分代回收 """ """ 重点: 1.GIL不是python的特点而是CPython解释器的特点 2.GIL是保证解释器级别的数据的安全 3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势(******) 4.针对不同的数据还是需要加不同的锁处理 5.解释型语言的通病:同一个进程下多个线程无法利用多核优势 """
十 GIL与普通互斥锁的区别
from threading import Thread,Lock import time mutex = Lock() money = 100 def task(): global money # with mutex: # tmp = money # time.sleep(0.1) # money = tmp -1 mutex.acquire() tmp = money time.sleep(0.1) # 只要你进入IO了 GIL会自动释放 money = tmp - 1 mutex.release() if __name__ == '__main__': t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(money) """ 100个线程起起来之后 要先去抢GIL 我进入io GIL自动释放 但是我手上还有一个自己的互斥锁 其他线程虽然抢到了GIL但是抢不到互斥锁 最终GIL还是回到你的手上 你去操作数据 """
十一 同一个进程下的多线程无法利用多核优势,是不是就没有用了
""" 多线程是否有用要看具体情况 单核:四个任务(IO密集型计算密集型) 多核:四个任务(IO密集型计算密集型) """ # 计算密集型 每个任务都需要10s 单核(不用考虑了) 多进程:额外的消耗资源 多线程:介绍开销 多核 多进程:总耗时 10+ 多线程:总耗时 40+ # IO密集型 多核 多进程:相对浪费资源 多线程:更加节省资源
代码验证
# 计算密集型 # from multiprocessing import Process # from threading import Thread # import os,time # # # def work(): # res = 0 # for i in range(10000000): # res *= i # # if __name__ == '__main__': # l = [] # print(os.cpu_count()) # 获取当前计算机CPU个数 # start_time = time.time() # for i in range(12): # p = Process(target=work) # 1.4679949283599854 # t = Thread(target=work) # 5.698534250259399 # t.start() # # p.start() # # l.append(p) # l.append(t) # for p in l: # p.join() # print(time.time()-start_time) # IO密集型 from multiprocessing import Process from threading import Thread import os,time def work(): time.sleep(2) if __name__ == '__main__': l = [] print(os.cpu_count()) # 获取当前计算机CPU个数 start_time = time.time() for i in range(4000): # p = Process(target=work) # 21.149890184402466 t = Thread(target=work) # 3.007986068725586 t.start() # p.start() # l.append(p) l.append(t) for p in l: p.join() print(time.time()-start_time)
总结
""" 多进程和多线程都有各自的优势 并且我们后面在写项目的时候通常可以 多进程下面再开设多线程 这样的话既可以利用多核也可以介绍资源消耗 """