网络并发编程05--终

1. GIL全局解释器锁

面试问到频率比较高


In CPython, the global interpreter lock, 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同一个进程内的多个线程无法利用cpu的多核优势(无法并行,但是可以并发)
3. 同一个进程内的多个线程要想运行必须先抢GIL锁。
4. 所有的解释型语言,几乎都无法实现同一个进程下的多个线程同时被运行


2. 验证GIL 的存在及功能


# 验证GIL.py

from threading import Thread
import time


m = 100

def test():
    global m
    tmp = m
    tmp -= 1
    m = tmp

for i in range(100):
    t = Thread(target=test)
    t.start()

time.sleep(3)
print(m)  # 0


#*************************************#

m = 100

def test():
    global m
    tmp = m
    time.sleep(1)
    tmp -= 1
    m = tmp
	
	
for i in range(100):
    t = Thread(target=test)
    t.start()

time.sleep(3)
print(m)  # 99


"""
同一个进程下的多个线程虽然会有 GIL 的存在,不会出现并行的效果,
但是如果线程内有IO操作还是会造成数据的错乱,这个时候需要我们额外的添加互斥锁。

"""

3. 死锁现象

from threading import Thread, Lock

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 释放了b锁' % self.name)


    def func2(self):
        b.acquire()
        print('%s 抢到了b锁' % self.name)
        a.acquire()
        print('%s 抢到了a锁' % self.name)
        a.acquire()
        print('%s 释放了b锁' % 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+

        开设多线程无法利用多核优势  40s+


"""
可以多进程结合多线程
"""

################  代码  ##############

from threading import Thread
from multiprocessing import Process
import threading
import os, time


def work():
    time.sleep(2)
	

if __name__ == '__main__':
    l = []
    print(os.cpu_count())
    start = time.time()
    for i in range(400):
        p = Process(target=work)
        # p = Thread(target=work)
        l.append(p)
    for p in l:
        p.join()
    stop = time.time()
    print('run time is %s' %(stop-start))

	

#***************************#
"计算密集型"

from multiprocessing import Process
from threading import Thread
from os, time


def work():
    res = 0
    for i in range(20000000):
        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 = ProcessPoolExecutor()  # 池子里面的进程数默认与当前计算机cpu核数保持一致

# 线程池
tpool = ThreadPoolExecutor()  # 池子里面的线程数默认与当前计算机cpu核数的5倍

def task(n):
    print('开始')
    res = 1
    for i in range(n):
        res = res += 1
    time.sleep(1)
    # print(res)
    return res

# 向线程池中提交任务
obj_list = []
for i in range(100):
    ret = pool.submit(task, i)  # 异步提交任务
    obj_list.append(ret)


pool.shutdown()  # 等待池子中的所有任务运行完毕之后再关闭池子并往后运行

for i in obj_list:
    print(ret.result())  # 同步提交:提交完任务原地等待任务的结果
    # print('主')

6. 进程池与线程池基本使用

from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
import time
import os


# 线程池
pool = ThreadPoolExecutor(5)  # 可以自定义线程数,也可以采用默认策略

# 定义一个任务
def task(n):
    print(n)
    time.sleep(2)
    return '>>>:%s' % n**2
   

# 向线程池中提交任务

obj_list = []
for i in range(20):
    res = pool.submit(task, i)  # 异步提交
    obj_list.append(res)
    # print(res.result())  # 获取任务的执行结果,同步提交
  
for i in obj_list:
    print(i.result())
   
# 等待线程池中所有的任务执行完毕之后,再获取各自任务的结果,
pool.shutdown()
for i in obj_list:
    print(i.result())
# jin程池
pool = ProcessPoolExecutor(5)  # 可以自定义进程数,也可以采用默认策略

# 定义一个任务
def task(n):
    print(n,os.getpid())
    time.sleep(2)
    return '>>>:%s' % n**2
   

# 定义一个回调函数:异步提交完之后有结果自动调用该函数
def call_back(a):
    print('异步回调函数:%s' % a.result)
    

# 向进程池中提交任务
if __name__ == '__main__':
    for i in range(20):
        res = pool.submit(task, i).add_done_callback(call_back)

    
  
"""
同步:提交完任务之后,原地等待任务的返回结果,期间不做任何事情,

异步:提交完任务之后,不原地等待任务的返回结果,结果由异步回调机制自动反馈

"""
# pool.shutdown()
# for i in obj_list:
#    print(i.result())

7. 协程理论与实操

进程
	资源单位
   
线程
	工作单位
   
协程
	是程序员单方面意淫出来的名词>>>:单线程下实现并发
  
# cpu被剥夺的条件
	1.程序长时间占用
    2.程序进程IO操作
  
# 并发
	切换 + 保存状态
    以往学习是:多个任务(进程,线程)来回切换
   
# 欺骗cpu的行为
单线程下我们如果能够自己监测IO操作,并且自己实现代码层面的切换,
那么对于cpu而言我们这个程序就没有IO操作
cpu会尽可能的被占用


"""代码层面"""
第三方gevent模块:能够自主监测IO行为并切换

代码

from gevent import monkey;monkey.patch_all()  # 固定代码格式,加上之后才能监测所有的io行为
from gevent import spawn
import time


def play(name):
    print('%s play 1' % name)
    time.sleep(5)
    print('%s play 2' % name)
   

def eat(name):
    print('%s eat 1' % name)
    time.sleep(3)
    print('%s eat 2' % name)
   

start = time.time()
play('jack')
eat('jack')

g1 = spawn(play, 'jason')  # 异步提交,
g2 = spawn(eat, 'jason')  # 异步提交

g1.join()
g2.join()  # 等待被监测的任务执行完毕
# joinall([g1,g2])

print('主', time.time() - start)
# 单线程下实现并发,提示效率

协程实现TCP服务端并发的效果

# 一个服务端同时服务多个客户端
 
import socket
from gevent import monkey;monkey.patch_all()
from gevent import spawn


server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen()


def talk(conn):
    while True:
        try:
            data = conn.recv(1024)
            print(data)
            conn.send(data+b'hello baby!')
        except ConnectionResetError as e:
            print(e)
            conn.close()
            break
            
def servers():
    server = socket.socket()
	server.bind(('127.0.0.1', 8080))
	server.listen()
    
    while True:
        conn, addr = server.accept()
        talk(conn)
 

if __name__ == '__main__':
    g1 = spawn(servers)
    g1.join()
# 客户端开设几百个线程发消息即可

"""
最nb的情况:多进程下开多线程,多线程下开协程

自己以后可能动手写的不多,一般都是使用别人封装好的模块或框架

"""

客户端

image-20220118102624229

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('utf8'))
        data = client.recv(1024)
        print(data.encode('utf8'))
   
if __name__ == '__main__':
    for i in range(500):
        t = Thread(target=client)
        t.start()

3. IO模型

"""理论为主,代码实现,大部分为伪代码(没有实际含义,仅为验证参考)"""
# IO模型研究的主要是网络IO

# 同步    synchronous
大部分缩写 sync
# 异步    asynchronous
         async
    
# 阻塞    blocking
# 非阻塞  non-blocking

四种IO模型

image-20220118104710673

# 阻塞IO
最为常见的一种IO 模型,有两个等待的阶段
waiting for data

# 非阻塞IO
系统调用阶段变为了非阻塞(轮询),有一个等待的阶段
copy data
轮询的阶段是比较消耗资源的

# 多路复用IO
利用select或者epoll来监管多个程序,一旦某个程序需要的数据存在于内存中了,那么立刻通知该程序去取即可

# 异步IO
只需要发起一次系统调用,之后无需频繁发送,有结果并准备好之后会通过异步回调机制反馈给调用者

posted @ 2022-01-18 16:21  Joshua_jiaxue  阅读(39)  评论(0编辑  收藏  举报