从小白到小黑 python学习之旅 日常总结 40(并发编程之多线程 操作)
开启线程的两种方式
第一种方式
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(1) print('%s is over'%name) # 开启线程不需要在main下面执行代码 直接书写就可以 # 但是我们还是习惯性的将启动命令写在main下面 t = Thread(target=task,args=('egon',)) t.start() # 创建线程的开销非常小 几乎是代码一执行线程就已经创建了 print('主') #egon is running #主 #egon is over
第二种方式
from threading import Thread import time class MyThead(Thread): def __init__(self, name): """针对刷个下划线开头双下滑线结尾(__init__)的方法 统一读成 双下init""" # 重写了别人的方法 又不知道别人的方法里有啥 你就调用父类的方法 super().__init__() self.name = name def run(self): print('%s is running'%self.name) time.sleep(1) print('egon DSB') if __name__ == '__main__': t = MyThead('egon') t.start() print('主') #egon is running #主 #egon DSB
TCP服务端实现并发的效果
""" 服务端 1.要有固定的IP和PORT 2.24小时不间断提供服务 3.能够支持并发 """ import socket from threading import Thread server =socket.socket() # 括号内不加参数默认就是TCP协议 server.bind(('127.0.0.1',8080)) server.listen(5) def talk(conn): # 通信循环 while True: try: data = conn.recv(1024) # 针对mac linux 客户端断开链接后 if len(data) == 0: break print(data.decode('utf-8')) conn.send(data.upper()) except ConnectionResetError as e: print(e) break conn.close() # 链接循环 while True: conn, addr = server.accept() t = Thread(target=talk,args=(conn,)) t.start()
线程对象的join方法 #同进程
from threading import Thread import time def task(name): print('%s is running'%name) time.sleep(3) print('%s is over'%name) if __name__ == '__main__': t = Thread(target=task,args=('egon',)) t.start() t.join() # 同进程 print('主') #egon is running #egon is over #主
同一个进程下的多个线程数据是共享的
from threading import Thread money = 100 def task(): global money money = 666 print(money) if __name__ == '__main__': t = Thread(target=task) t.start() t.join() print(money) #666 #‘子’线程 #666 #‘主’线程
线程对象及其他方法
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) # 获取线程名字 #hello world 11484 #hello world Thread-1 #hello world 11484 #hello world Thread-2 #主 2 #主 11484 #主 MainThread
守护线程
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('主') #egon is running #主
迷惑性的例子
""" 主线程运行结束之后不会立刻结束 会等待所有其他非守护线程结束才会结束 因为主线程的结束意味着所在的进程的结束 """ 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('主.......') #123 #456 #主....... #end123 #end456
线程互斥锁
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) #0
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.) ''' 结论:在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本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。
可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。
重点:
1.GIL不是python的特点而是CPython解释器的特点
2.GIL是保证解释器级别的数据的安全
3.GIL会导致同一个进程下的多个线程的无法同时执行即无法利用多核优势(******)
4.针对不同的数据还是需要加不同的锁处理
5.解释型语言的通病:同一个进程下多个线程无法利用多核优势
参考:https://www.cnblogs.com/linhaifeng/articles/7449853.html#_label5
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) #0
计算密集型
推荐用多进程
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)
总结
多进程和多线程都有各自的优势
并且我们后面在写项目的时候通常可以
多进程下面再开设多线程
这样的话既可以利用多核也可以介绍资源消耗

浙公网安备 33010602011771号