"""
开进程和开线程的步骤基本都是一样的 只是导入的模块不一样而已
开进程代码必须写在main下面而开线程则无需这么做
类的对象调用方法
类的继承重写run方法
"""
"""将接客与服务的活分开"""
"""等待当前线程对象结束之后 再继续往下执行"""
"""
同一个进程内可以开设多个线程
进程:资源单位
线程:执行单位
"""
"""
current_thread
active_count
"""
"""
主线程必须等待所有非守护线程的结束才能结束
t.daemon = True
t.start()
"""
"""
当多个线程在操作同一份数据的时候可能会造成数据的错乱
这个时候为了保证数据的安全,我们通常都会加锁处理
锁:
将并发变成串行,降低了程序的运行效率但是保证了数据的安全
行锁 表锁
"""
GIL全局解释器锁
"""
1.GIL是cpython解释器的特点 不是python的特点
2.GIL本质也是一把互斥锁,但是它是解释器级别的
3.它的存在是因为cpython解释器内存管理不是线程安全的
垃圾回收机制
引用计数
标签清除
分代回收
4.也就意味着GIL的存在导致了同一个进程下的多个线程无法利用多核优势(不能同时运行)
5.针对不同的数据应该加不同的锁来保证安全
"""
- 验证python多线程是否有用
"""
应该结合任务的具体类型再做判断
应该对任务分两种情况讨论
IO密集型 多线程更加节省资源
计算密集型 多进程更加合理
多进程多线程都有他们独特的应用场景
"""
死锁与递归锁
当你知道锁的使用枪锁必须要释放锁,其实你在操作的时候也极其任意产生死锁现象(整个程序卡死 阻塞)
from threading import Thread, Lock
import time
mutexA = Lock()
mutexB = Lock()
# 类只要加括号多次 产生的肯定是不同的对象
# 如果你想要实现多次加括号等到的是相同的对象 单例模式
class MyThead(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
mutexA.acquire()
print('%s 抢到A锁' % self.name) # 获取当前线程名
mutexB.acquire()
print('%s 抢到B锁' % self.name) # 获取当前线程名
mutexB.release()
mutexA.release()
def func2(self):
mutexB.acquire()
print('%s 抢到B锁' % self.name) # 获取当前线程名
time.sleep(2)
mutexA.acquire()
print('%s 抢到A锁' % self.name) # 获取当前线程名
mutexA.release()
mutexB.release()
if __name__ == '__main__':
for i in range(10):
t = MyThead()
t.start()
"""
递归锁的特点:
可以被连续的acquire和release
但是只能被第一个抢到这把锁执行上述操作
它的内部有一个计数器 每acquire一次计数加一 每release一次计数减一
只要计数不为0 那么其他人都无法抢到该锁"""
# 将上述的
mutexA = Lock()
mutexB = Lock()
# 换成
mutexA = mutexB = RLock()
信号量在不同的阶段可能对应不同额技术点
在并发编程中信号量指的是锁!
"""
如果我们将互斥锁比喻成一个厕所的话
那么信号量就相当于多个厕所
"""
from threading import Thread, Semaphore
import time
import random
sm = Semaphore(5) # 括号内写数字 写几就表示开设几个坑位
def task(name):
sm.acquire()
print('%s 正在蹲坑' % name)
time.sleep(random.randint(1, 5))
sm.release()
if __name__ == '__main__':
for i in range(20):
t = Thread(target=task, args=('伞兵%s号' % i,))
t.start()
一些进程/线程需要等待另外一些进程/线程运行完毕之后才能运行,类似于发射信号一样
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)
if __name__ == '__main__':
t = Thread(target=light)
t.start()
for i in range(20):
t = Thread(target=car, args=('%s' % i,))
t.start()
- 线程Q
import queue
# 我们现在使用的队列都是只能在本地测试使用
# 队列q 先进先出
q = queue.Queue(3)
q.put(1)
q.get()
q.get_nowait()
q.full()
q.empty()
# 后进后出q
q = queue.LifoQueue(3) # last in first out
q.put(1)
q.put(2)
q.put(3)
print(q.get()) # 3
# 优先级q 你可以给放入队列中的数据设置进出的优先级
q = queue.PriorityQueue(4)
q.put((10, '111'))
q.put((100, '222'))
q.put((0, '333'))
q.put((-6, '444'))
print(q.get())
# put括号内放一个元组 第一个放数字表示优先级
# 需要注意的是 数字越小优先级越高!
先回顾TCP服务端实现并发的效果
每来一个人就开设一个线程或者进程去处理
"""
无论是开设进程还是开设线程也好 都需要消耗资源
不过开设线程的消耗比开设进程的稍微小一点而已
我们是不可能做到无限制的开设进程和线程的 因为计算机硬件的资源跟不少
硬件的开发速度远赶不上软件
我们的宗旨应该是在保证计算机硬件能够整正常工作的情况下最大限度地利用它
"""
"""
# 池的概念
池是用来保证计算机硬件安全的情况下最大限度的利用计算机
它降低了程序的运行效率但是保证了计算机硬件的安全 从而让你写的程序能够正常运行
"""
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
pool = ThreadPoolExecutor(5) # 池子里面固定只有五个线程
# 括号内可以传数字 不传默认会开设当前计算机cpu个数五倍的线程
"""
池子造出来后 里面固定存在五个线程
这五个线程不会出现重复创建和销毁的过程
池子的使用非常简单
只需要将需要做的任务往池子中提交即可 自动会有人来服务你
"""
def task(n):
print(n)
time.sleep(2)
return n ** n
"""
任务的提交方式
同步:提交任务之后原地等待任务的返回结果 期间不做任何事
同步:提交任务之后不等待人物的返回结果 执行完继续往下执行
"""
# pool.submit(task, 1) # 朝池子中提交任务 异步提交
# print('主')
t_list = []
for i in range(20): # 朝池子中提交20个任务
res = pool.submit(task, i)
print(res.result()) # result方法 同步提交
t_list.append(res)
# 等待线程池中所有的任务执行完毕之后再继续往下执行
pool.shutdown() # 关闭线程池 等待线程池中所有的任务运行完毕
for t in t_list:
print('>>>:', t.result()) # 有序返回
"""
程序由并发变成了串行
任务答应的事None
res.result()拿到的就是异步提交的任务的返回结果
"""
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
pool = ProcessPoolExecutor(5)
res = pool.submit(task, i).add_done_callback(call_back)
"""
进程:资源单位
线程:执行单位
携程:这个概念完全是程序员自己意淫出来的 根本不存在
单线程下实现并发
程序员自己在代码层面上检测我们所有的IO操作
一旦遇到IO 在代码级别完成切换
这样给CPU的感觉是这个程序一直在运行 ,没有IO
从而提升程序的运行效率
多道技术
切换+保存状态
CPU两种切换
1.程序遇到IO
2.程序长时间占用
TCP服务端
accept
代码如何做到
切换+保存状态
切换
切换不一定是提升效率 也有可能是降低效率
保存状态
保存上一次我执行的状态 下一次来接着上一次的操作继续往后执行
yield
"""
import time
# 串行执行计算密集型的任务
def func1():
for i in range(1):
i + 1
def func2():
for i in range(1):
i + 1
start_time = time.time()
func1()
func2()
print(time.time() - start_time)
import time
# 切换 + yield
def func1():
while True:
100 + 1
yield
def func2():
g = func1() # 先初始化生成器
for i in range(1000):
i + 1
next(g)
start_time = time.time()
func2()
print(time.time() - start_time)
from gevent import monkey;monkey.patch_all()
import time
from gevent import spawn
"""
gevent模块本身无法检测常见的一些io操作
在使用的时候需要额外的导入一句话(猴子补丁)
from gevent import monkey
monkey.patch_all()
又由于上面的两句话在使用gevent模块的时候是肯定要导入的
所以还支持简写
from gevent import monkey;monkey.patch_all()
"""
def heng():
print('哼')
time.sleep(2)
print('哼')
def ha():
print('哈')
time.sleep(2)
print('哈')
start_time = time.time()
g1 = spawn(heng)
g2 = spawn(ha)
g1.join()
g2.join() # 等待被检测的任务执行完毕 再往后继续执行
print(time.time() - start_time)
"""
我们可以提供
多进程下面开设多线程
多线程下面开设在开设协程
"""