草庐IT

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

Kkkkangsh 2023-04-16 原文

目录

一、线程

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、网络并发编程理论与实操(三)的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  4. Python 相当于 Perl/Ruby ||= - 2

    这个问题在这里已经有了答案:关闭10年前。PossibleDuplicate:Pythonconditionalassignmentoperator对于这样一个简单的问题表示歉意,但是谷歌搜索||=并不是很有帮助;)Python中是否有与Ruby和Perl中的||=语句等效的语句?例如:foo="hey"foo||="what"#assignfooifit'sundefined#fooisstill"hey"bar||="yeah"#baris"yeah"另外,类似这样的东西的通用术语是什么?条件分配是我的第一个猜测,但Wikipediapage跟我想的不太一样。

  5. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  6. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  7. python - 如何读取 MIDI 文件、更改其乐器并将其写回? - 2

    我想解析一个已经存在的.mid文件,改变它的乐器,例如从“acousticgrandpiano”到“violin”,然后将它保存回去或作为另一个.mid文件。根据我在文档中看到的内容,该乐器通过program_change或patch_change指令进行了更改,但我找不到任何在已经存在的MIDI文件中执行此操作的库.他们似乎都只支持从头开始创建的MIDI文件。 最佳答案 MIDIpackage会为您完成此操作,但具体方法取决于midi文件的原始内容。一个MIDI文件由一个或多个音轨组成,每个音轨是十六个channel中任何一个上的

  8. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  9. 「Python|Selenium|场景案例」如何定位iframe中的元素? - 2

    本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决

  10. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

随机推荐