wenmo8 发布的文章

python语言中进程、线程、协程执行效率比较。

问题:python语言中 进程、线程、协程执行速度哪个最快?

在Python中,进程、线程和协程的执行速度不能简单地进行比较,因为它们的性能取决于多种因素,包括任务类型、I/O操作、CPU密集型计算、操作系统调度策略以及Python解释器的实现。

  1. 进程
    :进程是操作系统级别的资源隔离单位,每个进程都有自己的独立内存空间。在多核处理器系统上,多个进程可以同时在不同的CPU核心上运行,因此对于CPU密集型任务,如果能够充分利用多核优势,进程可能会表现出较高的执行速度。然而,进程之间的通信和数据共享通常涉及到昂贵的进程间通信(IPC)机制,如管道、消息队列或共享内存,这会增加额外的开销。

  2. 线程
    :线程是在同一进程中运行的轻量级执行单元,它们共享相同的内存空间。线程间的上下文切换比进程间的切换更快,因为不需要进行内存空间的切换。对于I/O密集型任务,由于可以利用多线程在等待I/O操作时进行其他工作,线程可能会提高程序的总体执行效率。然而,在Python中,由于全局解释器锁(GIL)的存在,即使在多核系统上,Python线程也无法实现真正的并行计算,对于CPU密集型任务,线程可能无法提供显著的性能提升。

  3. 协程
    :协程是一种用户态的轻量级线程,它们通过
    协作式调度
    而不是
    抢占式调度
    来实现并发。在Python中,asyncio库提供了对协程的支持。协程非常适合处理大量的I/O操作,因为它们可以在等待I/O完成时主动让出控制权,使得其他协程有机会运行,从而避免了线程上下文切换的开销。对于CPU密集型任务,协程的性能通常与普通函数调用相当。

总结:对于I/O密集型任务,协程通常能提供最快的执行速度,因为它可以有效地利用非阻塞I/O和事件循环来避免不必要的等待。对于CPU密集型任务,如果能有效利用多核处理器,多进程可能会表现出较快的速度。然而,实际的性能取决于许多因素,所以在设计和优化并发程序时,需要根据具体的应用场景和需求来选择合适的并发模型。

问题: CPU密集型任务和I/O密集型任务如何理解?
CPU密集型任务(计算密集型任务):

​ 这类任务的主要特点是需要大量的CPU运算能力,而对输入/输出操作(如磁盘读写、网络通信等)的需求相对较小。在执行过程中,CPU大部分时间都在进行计算操作,而不是等待外部资源的响应。例如,图像处理、视频编码解码、大数据分析、科学计算、机器学习算法的训练等都是典型的CPU密集型任务。

I/O密集型任务:

​ 这类任务的主要特点是大量依赖于输入/输出操作,比如从磁盘读取数据、向网络发送或接收数据等。在执行过程中,CPU可能会花费较多的时间等待这些I/O操作的完成,而实际的计算工作相对较少。例如,Web服务器处理HTTP请求、数据库查询、文件系统操作、网络爬虫等都是典型的I/O密集型任务。
​ 对于CPU密集型任务,通常通过提高CPU性能、使用多核处理器或者并行计算技术来提高效率。而对于I/O密集型任务,优化策略可能包括使用高效的I/O操作技术(如异步I/O、缓冲技术)、减少磁盘访问次数、合理设计数据结构和算法以降低I/O需求等。在多线程或多进程编程中,也需要考虑到这两种任务类型的特性,以便合理地分配和调度资源。

Python中全局解释器锁作用
  1. 目的
    : GIL的主要目的是防止多线程同时执行Python字节码,以确保数据安全性和一致性。由于Python的内存管理不是线程安全的,GIL通过禁止多个线程同时执行Python字节码来避免数据竞争和冲突。
  2. 工作原理
    : GIL是一个互斥锁(mutex),在CPython解释器中,每当有线程开始执行Python字节码时,它会先获取GIL。在执行完一定数量的字节码指令后,线程会释放GIL,并允许其他线程获取并执行Python字节码。
  3. 影响
    : GIL的存在意味着在多核CPU系统上,即使有多个线程,Python代码也无法实现真正的并行执行。这是因为无论有多少个线程或多少个CPU核心,任何时候都只有一个线程能够执行Python字节码。这在很大程度上限制了Python多线程程序在处理CPU密集型任务时的性能提升。

代码示例及分析:

  • 进程并发代码

根据任务数启动相应进程数并发执行

创建进程池,自定义进程数并发处理任务。

小结: 在执行同样任务并发情况下,优先推荐进程池;可以控制资源开销。 具体选择需要根据执行的任务类型来选择。对于CPU密集型任务,如果需要利用多核CPU的优势,可以使用多进程(
multiprocessing
模块)代替多线程,因为每个进程都有自己的Python解释器和独立的GIL,因此可以并行执行。

  • 线程并发代码

创建线程池,自定义线程数并发处理任务。

根据任务数开启相应线程进行任务并发执行。

小结:线程更适合处理I/O密集型任务,由于全局解释器锁(GIL)的存在,多线程并不能实现真正的并行计算。线程不能在同一时刻并行执行CPU密集型任务,但对于网络请求这样的I/O密集型任务,由于大部分时间都在等待网络响应,线程可以在等待期间切换到其他线程,从而实现某种程度的并发执行。

  • 异步协程并发代码

小结:协程通过异步I/O和事件循环机制,能够在单个线程中实现并发执行多个HTTP请求。虽然协程在同一时刻不会真正并行执行CPU密集型任务(受到全局解释器锁GIL的影响),但对于I/O密集型任务,如网络请求,协程可以提供高效的并发处理。

总结:使用python语言执行并发任务时,该如何选择编程模型需要考虑到执行任务的类型(CPU密集型or I/O密级型)、机器的硬件资源、任务的复杂度等这几个关键因素;选取合适的效率才可达到最佳。

Jetbrains fleet 配置 C++开发环境

1. 安装 Jetbrains Fleet

到Fleet下载页面下载Toolbox并安装
Jetbrains-Fleet下载页
安装完成后在任务栏打开 Toolbox ,在列表中选择安装 fleet。

2. 为 Fleet 准备 Workspace

在适当的地方建立文件夹作为 fleet 的工作空间,并在 fleet 中打开。

3. 配置 run.json 编译脚本

点击运行按钮,选择
Create Run Configuration
将其中内容配置如下

{
    "configurations": [
        {
            "type": "command",
            "name": "CMake Build",
            "program": "cmake",
            "args": ["-DCMAKE_BUILD_TYPE=Debug","-G","MinGW Makefiles","-S", "$FILE_DIR$", "-B", "$FILE_DIR$\\cmake-build-debug"],
        },
        {
            "type": "command",
            "name": "Make",
            "program": "mingw32-make",
            "args": ["-C","$FILE_DIR$\\cmake-build-debug"],

        },
        {
            "type": "command",
            "name": "Run",
            "program": "$FILE_DIR$\\cmake-build-debug\\$FILE_NAME_NO_EXT$.exe",
        },
        {
            "type": "command",
            "name": "Build And Run",
            "program": "$FILE_DIR$\\cmake-build-debug\\$FILE_NAME_NO_EXT$.exe",
            "dependsOn": ["CMake Build", "Make"],
        },
        {
            "type": "command",
            "name": "Example Build",
            "program": "g++",
            "args": ["$FILE$","-o","$FILE_DIR$\\$FILE_NAME_NO_EXT$.exe"]
        },
        {
            "type": "command",
            "name": "Example Run",
            "program": "$FILE_DIR$\\$FILE_NAME_NO_EXT$.exe",
        },
        {
            "type": "command",
            "name": "Example Build And Run",
            "program": "$FILE_DIR$\\$FILE_NAME_NO_EXT$.exe",
            "dependsOn": ["Example Build"],
        },
    ]
}

4. 安装 CMAKE 与MinGW-w64

CMAKE下载页面
MinGW-w64预编译二进制文件下载页面

CMAKE 直接安装即可,MinGW-w64 找个地方解压出来即可。
之后将 CMAKE 和 MinGW 的 bin 文件夹添加进系统 path 变量。

5. 运行配置如何使用

5.1 单个文件的编译运行

在工作空间下新建项目文件夹,在内部建立单个 cpp 文件并编写程序。
点击运行按钮,其中带 Example 前缀的是用于单个文件运行的配置:

  • Example Build —— 编译该文件
  • Example Run —— 不重新编译,运行上次编译的结果
  • Example Build And Run —— 编译并运行当前文件

5.2 以 CMAKE 管理的项目的编译运行

在工作空间下新建项目文件夹,内部项目以 CMAKE 管理。

  • CMakeLists.txt 中的项目名需要与作为 main 文件的文件名一致,例如主文件为
    helloworld.cpp
    ,则 CMAKE 中的项目名应为
    helloworld
  • 如果要使用 fleet 的 Smart Mode,CMakeLists.txt 中需要添加
    set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
    生成编译数据库

点击运行按钮,其中
不带
Example 前缀的是用于项目运行的配置:

  • CMake Build —— 执行 CMAKE 项目构建,生成项目 Makefile 文件与编译数据库
  • Make —— 编译项目
  • Run —— 不重新编译,运行上次编译的结果
  • Build And Run —— 编译项目并运行

大语言模型是基于自然语言的人工智能,所以它在语言上的表现相当出色,使用大语言模型进行可以进行诸如翻译、语气转换、润色、语言评价、扩写、润色等语言处理,对于日常文字工作,它是一名合格甚至优秀的私人秘书。
(一)翻译
在不同语言之间进行翻译是众多大语言模型都支持的特性,相对于翻译软件而言,语言模型的翻译往往更加出色,并且它可以根据指定的场景翻译出更地道的语言。
请将以下由双引号包含的文本翻译成中文,分别展示成正式与非正式两种语气: "Would you like to order a pillow?"

上述示例中是将英文翻译成中文,我们可以很容易感受到中文翻译结果的正式与非正式语气。其他语言之间的翻译亦是如此。
中英互译是我们最常见的需求,以下示例则演示了由中文翻译为英文的情况。
请将以下由双引号包含的文本翻译成英文,分别使用口语与书面语两种形式。
"我希望你能出席我的生日聚会。"

(二)语气风格调整
在现实世界中,同一件事情在不同场景下,我们沟通时的语气与风格会有很大的不同,大语言模型可以帮助我们在这些不同语气风格之间进行转换。例如我们可以让大语言模型根据口语化的交流内容生成一封正式的商务邮件。
请将以下双引号包含的文本改写成正式的商务邮件。
“李总,上周你说的培训需求具体要求是什么来着?”


在实际应用中,我们也可以将语气风格调整和翻译一并使用,例如上例中我们可以要求邮件使用英文。
请将以下双引号包含的文本改写成正式的商务邮件,由于李总所在公司的工作语言是英语,邮件要求使用英语完成并遵循英语邮件的习惯与格式。
“李总,上周你说的培训需求具体要求是什么来着?”

(三)错别字及语法纠正
日常写作时,由于文字水平的欠缺或者大意,我们写出的文字或多或少都有一些错别字或者语法问题,大语言模型可以帮助我们纠正这类小问题。
请校对并更正以下由引号包含文本。
“春节临进,加强节日安全意识,大力普及安全教育,切实做到防范于未然”


英语改错题是英语考试试卷的重要组成部分,以下示例使用2022年高考英语全国乙卷的改错题来验证大语言模型的语言功底(在这个示例中,文新一言的结果不尽如人意,故而换用讯飞星火进行测试)。
请校对并更正以下由三个反引号包含文本。
```
We all know that cycling is a greatly exercise. A doctor tells me people who lives the longest are dancers and cyclists. Maybe it is because the combination of fresh air, smooth movement and exercise.
Whether you ride a bicycle, you don't use petrol. So they are not producing carbon dioxide and not cause air pollution. Just see how cars have been taken over our cities. They often run at high speeds, what may put our lives in danger. And there were traffic jams, too. Our cities will be better places if we replace cars with bicycle.
```


上述截图中,用方框标记了与参考答案对比的结果,绿色是与参考答案一致的地方,红色是参考答案有而大语言模型未识别出的地方,蓝色是大语言模型进行了修改而参考答案认为没有问题的地方。总体而言,得分率很不错,比起我高考时不知强了多少倍。
大语言模型的语言相关能力还有很多,比如对文章进行润色、归纳总结主题等,在实际应用中可以尝试让模型完成任务,并根据模型反馈的结果迭代调整,最终取得满意的结果。

这是小卷对分布式系统架构学习的第8篇文章,在写第2篇文章已经讲过服务发现了,现在就从组件工作原理入手,讲讲注册中心

以下是面试题:

某团面试官:你来说说怎么设计一个注册中心?

我:注册中心嘛,就要有服务发现的功能,服务启动后把自己的信息注册上去,然后消费端可以拉取生产者的信息嘛,然后就能调用了

某团面试官:那你说说分布式系统中注册中心怎么选型呢?Eureka、Zookpeer、Nacos、Etcd这些中间件,你实际用的时候怎么考虑的?

我:就用Nacos啊,公司里大家都用这个

面试官:......

面试官:我知道了,今天的面试就到这吧......

1.注册中心的功能

设计注册中心时,先来思考下面几个问题:

  • 服务如何注册
  • consumer如何知道provider
  • 注册中心怎么做到高可用,服务发现怎么做的

要想实现一个服务注册中心,必须具备以下功能:

  • 服务注册
    :provider在注册中心完成注册
  • 服务注销
    :服务提供者需在注册中心完成服务下线
  • 心跳检测
    :检查服务提供者的健康状态
  • 服务查询
    :消费者获取服务列表消息
  • 服务变更查询:服务发生变更时,消费者通过查询变更获取最新服务列表
  • 服务高可用

2.Zookeeper工作原理

我们还是选择一个比较活跃的中间件来展开学习注册中心的工作原理。

Zookeeper的文档官网:
Apache ZooKeeper

官网解释Zookeeper名字的由来是因为分布式系统就像个动物园(Zoo),目前国内Dubbo场景下大多选用Zookeeper作为注册中心。官方解释它使用一个具有层次结构的命名空间(类似文件系统),用斜杠(“/”)分隔路径元素,根节点是(“/”)表示,无父节点。通过znodes(ZooKeeper节点)存储数据,如果znode有子节点,则无法删除该znode。这些数据可以被多个应用程序共享,并提供高可用性和一致性。

2.1简单API

ZooKeeper的一个设计目标是提供一个非常简单的编程接口,这些接口故意设计得十分简单。然而,借此接口,您可以实现更高阶的操作,如同步原语、组成员管理、所有权等。编程接口支持以下操作:

  • create
    :在树中的某个位置创建一个节点。
  • delete
    :删除一个节点。
  • exists
    :测试某个位置是否存在节点。
  • get data
    :读取节点中的数据。
  • set data
    :向节点写入数据。
  • get children
    :获取节点的子节点列表。
  • sync
    :等待数据传播。

2.2 服务注册

客户端注册
:当一个服务提供者实例启动时,它会向Zookeeper注册到某一路径上。

服务实例通过创建一个znode(通常是临时节点)来注册自己。这个znode包含服务实例的元数据,注册路径为:
{service}/{version}/{ip:port}

示例:将我们的HelloService部署启动后,Zookeeper上会创建一个目录:

  • /HelloWorldService/1.0.0/192.168.1.100:8080

下面简述服务注册的过程:

  1. 服务提供者启动时,会将其服务名称,ip地址注册到注册中心;
  2. 服务消费者在第一次调用服务时,会通过注册中心找到相应的服务的IP地址列表,并缓存到本地。当消费者调用服务时,不再请求注册中心,而是直接通过负载均衡算法从IP列表中取一个服务提供者的服务器调用服务;
  3. 当服务提供者的某台服务器宕机或下线时,相应的ip会从服务提供者IP列表中移除。同时,注册中心会将新的服务IP地址列表发送给消费者机器,缓存在消费者本机;
  4. 当某个服务的所有服务器都下线了,那么这个服务也就下线了;
  5. 同样,当服务提供者的某台服务器上线时,注册中心会将新的服务IP地址列表发送给服务消费者机器,缓存在消费者本机;
  6. 服务提供方可以根据服务消费者的数量来作为服务下线的依据

客户端代码示例

ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);
String path = "/services/myService";
zk.create(path, "192.168.1.100:8080".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

2.3 心跳检测机制

服务的某台机器下线时,Zookeeper是如何感知到?

zookeeper提供了“心跳检测”功能,
它会定时向各个服务提供者发送一个请求(实际上建立的是一个 socket 长连接),如果长期没有响应,服务中心就认为该服务提供者已经“挂了”,并将其删除。

2.4 服务发现

有关服务发现的概念可以看我之前的文章:
分布式系统架构2:服务发现

上面说了有新的服务提供者出现时,注册中心是怎么将这些变化通知给消费者客户端的呢?

有2种方式:

  • 主动Pull方式
    :消费者定期从注册中心拉取最新的服务提供者列表,并更新本地缓存。如Eureka
  • 发布-订阅模式
    :消费者实时监听服务更新状态,采用的是监听器+回调机制。
    Zookeeper用的是发布-订阅模式
    ,客户端在服务节点设置监视器(Watcher)。当节点发生变化(如服务实例上线或下线)时,Zookeeper会通知所有设置了监视器的客户端

2.5 存在问题

Zookeeper作为注册中心不合适,缺少可用性。

zk会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30 ~ 120s, 且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。

而作为注册中心,可用性的要求高于一致性!

2.6 Zookeeper总结

  • Zookeeper的心跳检测,可以自动探测服务提供者机器的宕机或下线;
  • Zookeeper的Watch机制,可以将变更的注册列表推给服务消费者;
  • Zookeeper是CP模型,不太适合作为注册中心

.NET 响应式编程 System.Reactive 系列文章(一):基础概念

引言

在现代软件开发中,处理
异步事件

数据流
已经成为常见的需求,比如用户输入、网络请求、传感器数据等。这些数据流通常是
无限的、异步的、实时的
,而传统的编程方式往往无法优雅地处理这些情况。
响应式编程(Reactive Programming)
为我们提供了一种新的思路,帮助开发者更自然、更高效地管理数据流和异步事件。

在 .NET 中,响应式编程的核心库是
System.Reactive
,通常简称为
Rx
。本篇文章将介绍响应式编程的基础概念以及
System.Reactive
的核心组件,为后续深入学习奠定基础。


什么是响应式编程?

响应式编程(Reactive Programming)
是一种
声明式编程范式
,专注于
异步数据流

变化传播
。简单来说,它是一种处理
事件驱动

数据变化
的编程方式,可以让程序自动对外部的变化做出反应。

在响应式编程中:

  • 数据流可以是
    有界的

    无界的
    (无限的)。
  • 数据流的变化可以触发
    订阅者
    的行为。
  • 订阅者(Observer)可以随时
    订阅

    取消订阅
    这些数据流。

传统编程 vs. 响应式编程

传统编程 响应式编程
通过轮询来获取数据变化 自动响应数据流的变化
使用回调函数处理异步 通过订阅和流式操作符处理异步
不擅长处理无限数据流 专注于处理无限、异步的数据流


System.Reactive 概述

System.Reactive
是微软推出的
Reactive Extensions(Rx)
的实现,为 .NET 提供了一个强大的
观察者模式

操作符库
,让我们可以轻松地管理数据流和异步事件。

核心组件

组件 描述
IObservable<T> 表示一个数据流的
生产者
IObserver<T> 表示一个数据流的
消费者
(订阅者)
Subject<T> 既是
生产者
也是
消费者
操作符(Operators) 用于对数据流进行转换、过滤、组合等操作


观察者模式简介

System.Reactive
的核心是基于
观察者模式(Observer Pattern)
,这是一种常见的设计模式,广泛用于处理事件和回调。

观察者模式的核心接口

  1. IObservable

    (可观察对象)


    • 负责
      生产
      数据流。
    • 提供
      Subscribe
      方法,允许观察者订阅它的数据流。
  2. IObserver

    (观察者)


    • 负责
      消费
      数据流。
    • 定义了以下三个方法:
      • OnNext(T value)
        : 当有新数据时被调用。
      • OnError(Exception error)
        : 当数据流发生错误时被调用。
      • OnCompleted()
        : 当数据流结束时被调用。

简单的示例代码

using System;
using System.Reactive.Subjects;

public class Program
{
    public static void Main()
    {
        // 创建一个 Subject,它既是 IObservable 也是 IObserver
        var subject = new Subject<string>();

        // 订阅数据流
        subject.Subscribe(
            onNext: value => Console.WriteLine($"Received: {value}"),
            onError: error => Console.WriteLine($"Error: {error.Message}"),
            onCompleted: () => Console.WriteLine("Completed")
        );

        // 发布数据
        subject.OnNext("Hello");
        subject.OnNext("Reactive Extensions");
        subject.OnCompleted();
    }
}

输出结果:

Received: Hello
Received: Reactive Extensions
Completed


Observable vs. Task

许多人会将
Observable

Task
进行比较,因为它们都用于处理异步操作。但两者之间有一些显著的区别。

特性 Observable Task
数据流 多个值 / 无限值 单个值
生命周期 可被取消订阅 一次性操作
时间维度 持续的时间序列 单次完成的任务
支持的操作符 丰富的转换、过滤、组合操作符 少数操作符

简单总结:

  • Task
    更适合处理
    单次异步操作
  • Observable
    更适合处理
    连续的数据流

    多次异步事件


数据流的三个阶段

在响应式编程中,数据流有三个阶段:

  1. OnNext
    : 数据流的每一个值都会通过
    OnNext
    方法传递给订阅者。
  2. OnError
    : 如果数据流中出现错误,会通过
    OnError
    方法通知订阅者。
  3. OnCompleted
    : 当数据流结束时,会通过
    OnCompleted
    方法通知订阅者。


热数据流和冷数据流


System.Reactive
中,数据流可以分为两种类型:

1. 冷数据流(Cold Observable)

  • 冷数据流是
    被订阅时才开始产生数据
  • 每个订阅者都会从
    头开始
    接收数据。

示例:

var cold = Observable.Range(1, 5);
cold.Subscribe(x => Console.WriteLine($"Subscriber 1: {x}"));
cold.Subscribe(x => Console.WriteLine($"Subscriber 2: {x}"));

输出:

Subscriber 1: 1
Subscriber 1: 2
Subscriber 1: 3
Subscriber 1: 4
Subscriber 1: 5
Subscriber 2: 1
Subscriber 2: 2
Subscriber 2: 3
Subscriber 2: 4
Subscriber 2: 5

2. 热数据流(Hot Observable)

  • 热数据流是
    数据流开始时就产生数据
  • 每个订阅者会从
    当前数据流的位置
    开始接收数据。

示例:

var hot = new Subject<int>();
hot.OnNext(1);
hot.Subscribe(x => Console.WriteLine($"Subscriber: {x}"));
hot.OnNext(2);

输出:

Subscriber: 2


总结

在本篇文章中,我们介绍了响应式编程的基础概念以及
System.Reactive
的核心组件:

  • 响应式编程专注于处理
    异步数据流
  • System.Reactive
    提供了核心接口
    IObservable

    IObserver
  • 数据流的生命周期包含
    OnNext

    OnError

    OnCompleted
  • 区分了
    冷数据流

    热数据流

下一篇文章将介绍
System.Reactive
的基础操作符,包括创建、转换和过滤数据流的方法,敬请期待!