Python基础之网络编程:7、网络并发编程理论与实操(三)

一、线程

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()

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python基础之网络编程:7、网络并发编程理论与实操(三) - Python技术站

(0)
上一篇 2023年4月2日 下午4:24
下一篇 2023年4月2日 下午4:24

相关文章

  • Python基础之网络编程:2、OSI七层协议

    目录 Python基础之网络编程 一、网络编程前戏 二、OSI七层协议 1、七层协议简介: 2、五层协议详解: 2、1.物理连接层 2、2.数据链路层 网络相关专业名词 2、3.网络层 2、4.传输层 PORT协议(端口协议) TCP与UDP协议 1.TCP协议(重要) 2.UDP协议 tcp和udp的对比 5.应用层 Python基础之网络编程 一、网络编…

    2023年4月2日
    00
合作推广
合作推广
分享本页
返回顶部