一、线程
1、线程理论
进程与线程的区别:
-
进程:
- 进程是资源单位,表示一块内存空间
-
线程:
- 线程是执行单位,指在进程内的代码指令
可以将进程比喻成车间,线程就是车间里的流水线
一个进程内至少含有一个线程
线程的特点:
1、一个进程内可以开设多条线程
2、同一个进程下的线程之间数据是共享的
3、创建线程的消耗要小于进程(创建时间小于创建进程时间)
2、创建线程的两种方式
2、1.继承类创建
创建顺序:
1、导入 threading import Thread 模块
2、生成一个类
3、使用类继承 Thread
4、生成类体代码
5、使用类生成对象,并调用
6、每次调用的对象就是不同的线程
代码用法:
from threading import Thread
class MyThread(Thread):
def run(self):
print('run is running')
time.sleep(1)
print('run is over')
obj = MyThread()
obj.start()
print('主线程')
2、2.使用函数创建
创建顺序:
1、导入 threading import Thread 模块
2、生成函数(功能代码)
3、定义线程对象,并在参数内填入需要加载的子线程函数
4、使用进程对象加‘点’start()的方式启动子线程
代码用法:
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(0.1)
print(f'{name} is over')
if __name__ == '__main__':
for i in range(100):
t = Thread(target=task, args=('用户%s'%i,))
t.start()
3、线程的诸多特性
1、join()
使异步变为同步,使子线程代码执行结束向下执行主线程代码
2、多个线程之间数据共享
3、current_thread()
查看线程名字
4、active_count()
查看当前进程下线程的数量
二、GIL全局解释器锁
1、简介
官方简介:
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字节码。这个锁是必要的,主要是因为CPython的内存管理不是线程安全的。(然而,自从GIL存在以来,其他特性已经依赖于它强制执行的保证。
'''
简译:
1、在cpython解释器中,存在全局解释器锁,简称GIL
python解释器的类型有很多
cpython>>>:基于C开发的python (常用)
jpython>>>:基于Java开发的python
pypython>>>:基于py开发的python
2、GIL本质也是一把互斥锁,用来阻止同一个进程内多个线程的安全
同一个进程下的多个线程,如果在没有互斥锁的情况下,同样会产生对数据的处理不准确的情况
3、GIL的存在是因为CPython解释器中内存管理不是线程安全的
垃圾回收机制的本质也是一种线程,如果线程的本质不是串行的话,那么在我们每将定义一个变量时,就会被垃圾机制立马检测,将定义的数据清空
2、验证GIL的存在
推导流程:
1、导入线程模块
2、定义一个全局变量
3、定义一个函数体代码
4、生成多个线程模块
5、创建的多个线程修改全局变量
6、得到的结果是多个线程依次修改的
结论:
线程的执行是同步的,如果是异步的话那么修改的全局变量就会产生混乱
代码表现:
from threading import Thread
num = 100
def task():
global num
num -= 1
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(num)
3、GIL与普通互斥锁
虽然cpython的线程中自带GIL,但在创建多个线程时,任需要考虑到多种情况
- GIL只能确保同进程内的多个线程数据不被垃圾回收机制弄乱
- GIL并不能保证序列里数据的安全
- 在使用线程处理数据时,任需要针对各种情况加‘锁’
代码表现:
def task(mutex):
global num
mutex.acquire()
count = num
time.sleep(0.1)
num = count - 1
mutex.release()
mutex = Lock()
t_list = []
for i in range(100):
t = Thread(target=task,args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(num)
4、python多线程是否有用
因为cpython的线程自带GIL机制,那么处理数据起来就相对较慢,那么cpython的线程是否就很鸡肋呢,需要分以下情况
情况一:
单个cpu
多个cpu
情况二:
IO密集型(代码有IO操作)
计算密集型(代码没有IO操作)
1、单个cup + IO密集型
多进程打开内存空间较慢,而打开线程时间较短,所以这种情况线程有较大优势
# 多线程具有优势
2、单个CPU + 计算密集型
多进程打开内存空间较慢,而打开线程时间较短,所以这种情况线程有较大优势
# 多线程具有优势
3、多个CPU + IO密集型
多进程打开内存空间较慢,而打开线程时间较短,所以这种情况线程有较大优势
# 多线程具有优势
4、多个CPU + 计算密集型
多个CPU下进程计算的时间时多个进程的综合,而线程是多个子线程的总和
# 多进程具有优势
'''
总结: IO密集型时多线程具有优势
计算密集型时多进程相对具有优势
'''
代码表现:
def work():
# 计算密集型
res = 1
for i in range(1, 100000):
res *= i
if __name__ == '__main__':
# print(os.cpu_count()) # 12 查看当前计算机CPU个数
start_time = time.time()
# p_list = []
# for i in range(12): # 一次性创建12个进程
# p = Process(target=work)
# p.start()
# p_list.append(p)
# for p in p_list: # 确保所有的进程全部运行完毕
# p.join()
t_list = []
for i in range(12):
t = Thread(target=work)
t.start()
t_list.append(t)
for t in t_list:
t.join()
print('总耗时:%s' % (time.time() - start_time)) # 获取总的耗时
"""
计算密集型
多进程:5.665567398071289
多线程:30.233906745910645
"""
def work():
time.sleep(2) # 模拟纯IO操作
if __name__ == '__main__':
start_time = time.time()
# t_list = []
# for i in range(100):
# t = Thread(target=work)
# t.start()
# for t in t_list:
# t.join()
p_list = []
for i in range(100):
p = Process(target=work)
p.start()
for p in p_list:
p.join()
print('总耗时:%s' % (time.time() - start_time))
"""
IO密集型
多线程:0.0149583816528320
多进程:0.6402878761291504
"""
5、死锁现象
死锁现象是指,当一个进程下开设了多个‘锁’时,在多个功能体代码执行时,经历抢锁阶段时,如果遇到IO操作,那么就会相互‘尬’住,这种情况就称之为死锁现象
代码表现:
acquire()
release()
from threading import Thread,Lock
import time
mutexA = Lock() # 产生一把锁
mutexB = Lock() # 产生一把锁
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexB.acquire()
print(f'{self.name}抢到了B锁')
mutexB.release()
print(f'{self.name}释放了B锁')
mutexA.release()
print(f'{self.name}释放了A锁')
def func2(self):
mutexB.acquire()
print(f'{self.name}抢到了B锁')
time.sleep(1)
mutexA.acquire()
print(f'{self.name}抢到了A锁')
mutexA.release()
print(f'{self.name}释放了A锁')
mutexB.release()
print(f'{self.name}释放了B锁')
for i in range(10):
obj = MyThread()
obj.start()
三、信号量
1、简介
在python中信号量相当于一次性定义了多把互斥锁,当我们创建多个线程、进程时,就会按照定义好的信号量去执行代码,当信号量中代码执行结束后,没有抢到信号量的进程、代码就会再次去抢锁,以此来完成工作
2、使用方法
1、导入信号量模块
关键词:Semaphore
from thread import Semaphore
2、产生信号量
3、定义进程或线程
4、在功能代码中加入锁
代码用法:
from threading import Thread, Lock, Semaphore
import time
import random
sp = Semaphore(5) # 一次性产生五把锁
class MyThread(Thread):
def run(self):
sp.acquire()
print(self.name)
time.sleep(random.randint(1, 3))
sp.release()
for i in range(20):
t = MyThread()
t.start()
四、event事件
1、简介
event事件是指,可使子进程、线程等待主进程、线程指令后进程操作
2、代码用法
1、导入event模块
from thread import evevt
2、定义一个event对象
3、将对象set()方法添加至主线程、进程功能代码后
4、将wait()方法添加至线程、进程执行代码前
代码用法:
from threading import Thread, Event
import time
event = Event() # 类似于造了一个红绿灯
def light():
print('红灯亮着的 所有人都不能动')
time.sleep(3)
print('绿灯亮了 油门踩到底 给我冲!!!')
event.set()
def car(name):
print('%s正在等红灯' % name)
event.wait()
print('%s加油门 飙车了' % name)
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('熊猫PRO%s' % i,))
t.start()
五、进程池与线程池
1、简介
进程和线程并不是可以无限创建的,因为CPU的公功效时有限的,肆意的创建线程、进程会导致计算机死机、崩溃
-
进程池
- 提前创建好固定数量的进程、供后续程序的调用,超出则等待
-
线程池
- 提前创建号固定数量的线程池,供后续程序的调用超出则等待
2、代码用法
1、导入模块
模块关键词:concurrent.futures
方法关键词:ProcessPoolExecutor (进程
ThreadPoolExecutor(线程)
2、定义进程池或线程池最大数量(固定池的数量)
3、直接将任务提交给定义的对象
代码用法:
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import os
import time
import random
from threading import current_thread
# 1.产生含有固定数量线程的线程池
# pool = ThreadPoolExecutor(10)
pool = ProcessPoolExecutor(5)
def task(n):
print('task is running')
# time.sleep(random.randint(1, 3))
# print('task is over', n, current_thread().name)
# print('task is over', os.getpid())
return '我是task函数的返回值'
def func(*args, **kwargs):
print('from func')
if __name__ == '__main__':
# 2.将任务提交给线程池即可
for i in range(20):
# res = pool.submit(task, 123) # 朝线程池提交任务
# print(res.result()) # 不能直接获取
# pool.submit(task, 123).add_done_callback(func)
六、协程
1、简介
-
进程
- 资源单位
-
线程
- 执行单位
-
协程
- 可以使单线程实现并发且效率比极高
协程使程序员自己想出来的办法,正常情况下代码遇到IO操作操作系统就会将CPU调走,协程使通过代码的方法,使程序遇到IO操作时,‘欺骗’CPU,使CPU检测不到程序的IO操作,继续向下执行,通过这种方式,实现单线程下的高并发
2、代码用法
1、导入模块
1、1.模块关键词:gevent
方法关键词:monkey;monkey.patch_all()
1、2.模块关键词:gevent
方法关键词:spawn
2、产生模块对象,后方参数内填入功能名
3、使用对象加‘点’join()的方式调用
代码用法:
import time
from gevent import monkey;
monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def func1():
print('func1 running')
time.sleep(3)
print('func1 over')
def func2():
print('func2 running')
time.sleep(5)
print('func2 over')
if __name__ == '__main__':
start_time = time.time()
# func1()
# func2()
s1 = spawn(func1) # 检测代码 一旦有IO自动切换(执行没有io的操作 变向的等待io结束)
s2 = spawn(func2)
s1.join()
s2.join()
print(time.time() - start_time) # 8.01237154006958 协程 5.015487432479858
3、协程实现并发
原理是通过‘猴子’补丁的方法监视IO操作的代码,当代码遇到IO操作时会自动调用下调用函数,通过将whil循环就可以单进程实现并发的效果
代码用法:
import socket
from gevent import monkey;monkey.patch_all() # 固定编写 用于检测所有的IO操作(猴子补丁)
from gevent import spawn
def communication(sock):
while True:
data = sock.recv(1024)
print(data.decode('utf8'))
sock.send(data.upper())
def get_server():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, addr = server.accept() # IO操作
spawn(communication, sock)
s1 = spawn(get_server)
s1.join()