GIL全局解释器
复习
- 僵尸进程
所有的进程在运行结束后并不会立即被销毁(父进程需要获取子进程的资源) - 孤儿进程
子进程正在运行但是产生该进程的父进程意外死亡 - 守护进程
守护进程的结束取决于守护的对象的进程何时结束 - 互斥锁
锁:将并发变成串行,牺牲了效率但是提高了数据的安全
mutex.erquier() 抢锁
mutex.release() 释放锁
注:锁虽然使用很方便,但不要轻易使用,容易造成死锁现象 - 生产者消费者模
确保供需平衡 - 线程理论
进程仅仅是一个资源单位,进程中真正干货的其实是线程(每一个线程肯定自带一个主线程)
开设线程的消耗远远小于开设进程的消耗,同一个进程的多个线程数据是共享的 - 线程其他特性
几乎与进程对象方法一致 - 消息队列简介
消息队列就是一个存放数据的地方,并且数据遵循先进先出的原则
只要用于解决生产者和消费者供需不平衡的问题
1、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.
1.python解释器有很多版本(默认的是Cpython)
Cpython,、Jpython,pypython
在Cpython中,GIL全局解释器锁,其实也是一把互斥锁,主要用于阻止同一个进程下的多个线程同时被运行(python的多线程无法使用多核优势)
GIL肯定存在于Cpython解释器中,主要原因就在于Cpython解释器的内存管理不是线程安全的
2.内存管理>>>垃圾回收机制
应用计数
标记清除
分代回收
"""
# 1.GIL是Cpython解释器的特点
# 2.python同一个进程内的多个线程无法利用多和优势(不能并行可以并发)
# 3.同一个进程内的多个线程要想运行必须想抢GIl锁
# 4.所有的解释性语言几乎都无法实现同一个进程下多个线程同时被运行
2、验证GIl的存在及其功能
from threading import Thread # 调用开启线程模块
import time # 调用时间模块
m = 100 # 定义变量
def test(): # 定义函数
global m # 声明更改全局名称空间的值
tmp = m # 指向100
# time.sleep(1) 添加这个打印就会变成99,重要
tmp -= 1 # 100 - 1
m = tmp # 重新指向
for i in range(100):
t = Thread(target=test) # 循环开启线程
t.start() # 启动线程
time.sleep(3) # 停止三秒钟
print(m) # 打印
# 打印为0
3、死锁现象
from threading import Thread, Lock
import time
A = Lock()
B = Lock()
class MyThread(Thread):
def run(self):
self.func1()
self.func2()
def func1(self):
A.acquire()
print('%s 抢到了A锁' % self.name) # current_thread().name 获取线程名称
B.acquire()
print('%s 抢到了B锁' % self.name)
time.sleep(1)
B.release()
print('%s 释放了B锁' % self.name)
A.release()
print('%s 释放了A锁' % self.name)
def func2(self):
B.acquire()
print('%s 抢到了B锁' % self.name)
A.acquire()
print('%s 抢到了A锁' % self.name)
A.release()
print('%s 释放了A锁' % self.name)
B.release()
print('%s 释放了B锁' % self.name)
for i in range(10):
obj = MyThread()
obj.start()
"""就算知道锁的特性及使用方式 也不要轻易的使用 因为容易产生死锁现象"""
4、python多线程是否没用
# 是否有用需要看情况而定(程序的类型)
"""
IO密集型
eg:
四个任务,每个任务耗时10s
开设多进程没有太大的优势:10s+
遇到IO就需要切换,并且开设进程还需要申请内存空间和拷贝代码
开设多线程有优势:10s+
不需要消耗额外的资源
计算密集型
eg:四个任务,每个任务耗时10s
开设多进程可以利用多核优势:10s+
开设多线程无法利用多核优势:10s+
可以多进程结合多线程
"""
"""IO密集型"""
from multiprocessing import Process
from threading import Thread
import thrading
import os, time
def work():
time.sleep(2)
if__name__ == "__main__":
l = []
print(os.cpu_count()) # 获取点前机器的核数
start = time.time()
for i in range(800):
# p = Process(target=work) # 创建进程
p = Thread(target=work) # 创建线程
l.append(p)
p.start()
for i in l:
p.join()
stop = time.time()
print("运行时间:%s"%(stop - start))
"""计算密集型"""
from multiprocessing import Process
from threading import Thread
import os,time
def work():
res=0
for i in range(100000000):
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))
5、进程池与线程池
思考:能否无限制开设进程或者线程?
不能,
如果从技术层面来讲无限制开设是可以的,并且是最高效的
但是从硬件方面来讲是无法实现的(硬件的发展永远比不上软件发展的速度)
池:
进程池:提前开设了固定个数的进程,之后反复调用这些进程完成工作(不再开设新的进程)
线程池:提前开设固定个数的线程,之后反复调用这些线程完成工作(之后不在开设新的线程)
# 代码实现进程池与线程池
# 高端代码
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
pool = ProcessPoolExecutir() # 开设进程池,括号内不添加数值那就与当前计算机的cpu数保持一致,内部有调佣当前CPU的数量的代码
pool = ThreadPoolExecutor() # 开设线程池,默认为CPU数量的倍
def task():
print('开始')
res = 1
for i in range(n):
res = res**i
time.sleep(1)
print(res)
# 向线程池中提交任务
pool.submit(task, 100)
print('主')
6、进程池与线程池基本使用
6、1线程池基本
"""1.0版本"""
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 创建线程池
pool = ThreadPoolExecutor(5)
# 定义一个任务
def tske(n):
print(n, os.getpid())
time.sleep(2)
return '>>>:%s'% n ** 2
# 等待所有线程运行完毕之后关闭线程池然后输出结果
obj_list = []
for i in range(20):
res = pool.submit(tske, i) # 向线程池内添加要开设的线程与任务需要的参数
obj_list.append(res) # 将线程的返回结果添加到空列表,
# print(res.result()) # 直接打印结果。这属于同步提交
pool.shutdown() # 等待所有线程运行结束后一次性打印线程提交的结果
for i in obj_list:
print(i.result()) # 等待结果全部都来在打印
"""1.1版本"""
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 创建线程池
pool = ThreadPoolExecutor(5)
# 定义一个任务
def tske(n):
print(n, os.getpid())
time.sleep(2)
return '>>>:%s'% n ** 2
# 定义一个回调函数,异步提交之后有结果立刻调用该函数
def call_back(a):
print('异步回调机制:%s'%a.result())
for i in range(20):
pool.submit(tske, i).add_done_callback(call_back) # 向线程池中提交任务,submit第一人参数是任务,第二个参数是任务的参数,之后点出方法,就是异步回调,添加的参数是函数
6、3进程池的基本使用
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 创建进程池
pool = ProcessPoolExecutor(5)
# 定义一个任务
def tske(n):
print(n, os.getpid())
time.sleep(2)
return n ** 2
# 定义一个回调函数,异步提交之后有结
def call_back(a):
print('异步回调机制:%s' % a.result())
start = time.time()
if __name__ == '__main__':
for i in range(10):
pool.submit(tske, i).add_done_callback(call_back)
pool.shutdown() # 等待所有进程运行完毕
# tske(20)
print(time.time()-start) # 计算时间
7、协程理论与实操
进程:
资源单位,类似工厂
线程:
每一个进程最少都要有一个线程,工作单位,类似工厂的流水线
协程:
程序员单方面意淫出来的名词>>>:只要作用单线程下实现并发
什么是并发:
切换+保存状态
以往学习的都是:多个任务(多个进程或者多个线程)来回切换
为何切换:
当程序碰到IO操作或者长时间占用CPU资源
协程具体需要什么才可以实现:(这里只针对单线程下的IO操作)
需要程序没有IO操作,或者让CPU感觉不到程序有IO操作,自己检测程序的IO操作并实现代码层面的切换
对于CPU而言这么程序没有IO,就会始终运行着这个程序,尽可能占用CPU资源
"""
代码层面
需要第三方模块gevent:能够自主检测IO行为并切换
"""
from gevent import monkey;monkey.patch() # 固定代码格式
from gevent import spawn
import time
def play(name):
print('%s play 1' % name)
time.sleep(5) # 程序中的IO操作
print('%s play 2' % name)
def eat(name):
print('%s eat 1' % name)
time.sleep(3) # 程序中的IO操作
print('%s eat 2' % name)
start = time.time()
# play('jason') # 正常的同步调用,调用函数
# eat('jason') # 正常的同步调用,调用函数
g1 = spawn(play, 'jason') # 异步提交
g2 = spawn(eat, 'jason') # 异步提交
g1.join()
g2.join() # 等待被监测的任务运行完毕
print('主', time.time() - start) # 单线程下实现并发,提升效率
8、协程实现TCP服务端的并发效果
8.1、最基本的TCP服务端并发
import socket
from threading import Thread
from multiprocessing import Process
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0: break
print(data.decode('utf8'))
sock.send(data + b'gun dan!')
except ConnectionResetError as e:
print(e)
break
sock.close()
while True:
sock, addr = server.accept()
print(addr)
# 开设多进程或者多线程
t = Thread(target=talk, args=(sock,))
t.start()
8.2、添加协程
"""服务端"""
import socket
from gevent import monkey;
monkey.patch_all()
from gevent import spawn
def talk(sock):
while True:
try:
data = sock.recv(1024)
if len(data) == 0: break
print(data)
sock.send(data + b'hello baby!')
except ConnectionResetError as e:
print(e)
sock.close()
break
def servers():
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()
while True:
sock, addr = server.accept()
spawn(talk, sock)
g1 = spawn(servers)
g1.join()
"""客户端"""
from threading import Thread, current_thread
from socket import *
def client():
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
n = 0
while True:
msg = '%s say hello %s' % (current_thread().name, n)
n += 1
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
9.1、IO模型
轮询:
不停的询问,直到有答案
9.1、阻塞IO
最为常见的io模型
有两个等待阶段
wiat for data, copy data

9.2、非阻塞IO
轮询是否有数据(非常消耗资源)
系统调用变成了非阻塞状态
只有一个等待阶段
copy data

9.3、IO多路复用
利用select/epoll来监管多个程序,一旦某个程序需要的数据存在内存中里,那么立即通知该程序去取即可
9.4、异步IO
发起一次系统调用,之后无需频繁发送,有结果并准备好之后会通知反馈机制反馈给调用方
异步调用+反馈机制
自动提醒(回调机制)

浙公网安备 33010602011771号