2024年3月

本文分享自华为云社区《
Python随机数探秘:深入解析random模块的神奇之处
》,作者:柠檬味拥抱。

标准库random函数大全:探索Python中的随机数生成

随机数在计算机科学和数据科学领域中扮演着重要角色,Python的标准库中提供了
random
模块,用于生成各种随机数。本篇博客将深入探讨
random
模块的各种函数,以及它们的应用场景和代码示例。

1. random.random()

random.random()
函数返回一个范围在[0.0, 1.0)之间的随机浮点数。这是生成均匀分布随机数的基础函数。

import random

random_number
=random.random()
print(
"随机浮点数:", random_number)

2. random.randint(a, b)

random.randint(a, b)
函数生成一个范围在[a, b]之间的随机整数。这在需要生成整数随机数时非常有用。

import random

random_integer
= random.randint(1, 10)
print(
"随机整数:", random_integer)

3. random.choice(seq)

random.choice(seq)
函数从序列
seq
中随机选择一个元素返回。适用于从列表、元组等序列中随机挑选元素的场景。

import random

my_list
= [1, 2, 3, 4, 5]
random_element
=random.choice(my_list)
print(
"随机选择的元素:", random_element)

4. random.shuffle(x)

random.shuffle(x)
函数用于将序列
x
中的元素随机排列,打乱原有顺序。

import random

my_list
= [1, 2, 3, 4, 5]
random.shuffle(my_list)
print(
"打乱后的列表:", my_list)

5. random.sample(population, k)

random.sample(population, k)
函数从总体
population
中随机选择
k
个不重复的元素。适用于需要获取不重复样本的情况。

import random

my_list
= [1, 2, 3, 4, 5]
sampled_list
= random.sample(my_list, 3)
print(
"随机抽样后的列表:", sampled_list)

6. random.uniform(a, b)

random.uniform(a, b)
函数生成一个范围在[a, b]之间的随机浮点数,类似于
random.random()
但可以指定范围。

import random

random_float
= random.uniform(1.0, 5.0)
print(
"指定范围的随机浮点数:", random_float)

这只是
random
模块中一小部分函数的介绍,该模块还包括其他函数,如
random.gauss()
用于生成高斯分布的随机数。通过灵活使用这些函数,可以满足各种随机数生成的需求。在实际应用中,深入了解这些函数的特性和用法,可以帮助提高程序的随机数生成效率和准确性。

7. random.seed(a=None, version=2)

random.seed(a=None, version=2)
函数用于初始化伪随机数生成器的种子。通过设置相同的种子,可以确保在不同的运行中获得相同的随机数序列,这对于调试和重现实验结果非常有用。

import random

random.seed(
42) # 设置随机数生成器的种子为42
random_number
=random.random()
print(
"固定种子下的随机浮点数:", random_number)

8. random.getrandbits(k)

random.getrandbits(k)
函数生成
k
比特长的随机整数。适用于需要生成指定位数的随机整数的情况。

import random

random_bits
= random.getrandbits(4) # 生成4比特长的随机整数
print(
"随机整数(4比特长):", random_bits)

9. random.randrange(start, stop[, step])

random.randrange(start, stop[, step])
函数生成一个在指定范围内以指定步长递增的随机整数。

import random

random_integer
= random.randrange(0, 10, 2) # 在0到10之间,以2为步长生成随机整数
print(
"随机整数(指定范围和步长):", random_integer)

10. random.random()

random.random()
函数在前面提到过,但值得注意的是,它生成的是伪随机数。如果需要更加随机的种子,可以结合使用
time
模块获取当前时间作为种子。

import random
import time

random.seed(time.time())
random_number
=random.random()
print(
"更加随机的浮点数:", random_number)

通过深入了解
random
模块的这些函数,你可以更好地利用Python进行随机数生成,满足各种应用场景的需求。无论是用于模拟实验、数据采样还是密码学领域,
random
模块提供了强大的工具来处理随机数。确保在实际应用中选择适当的函数,并根据需求设置合适的参数,以获得所需的随机性。

11. random.triangular(low, high, mode)

random.triangular(low, high, mode)
函数生成一个服从三角分布的随机浮点数,其中
low
是分布的最小值,
high
是最大值,
mode
是众数。

import random

random_triangular
= random.triangular(1, 5, 3) # 生成三角分布的随机数
print(
"三角分布的随机浮点数:", random_triangular)

12. random.betavariate(alpha, beta)

random.betavariate(alpha, beta)
函数生成一个服从Beta分布的随机浮点数,其中
alpha

beta
是分布的形状参数。

import random

random_beta
= random.betavariate(2, 5) # 生成Beta分布的随机数
print(
"Beta分布的随机浮点数:", random_beta)

13. random.expovariate(lambd)

random.expovariate(lambd)
函数生成一个服从指数分布的随机浮点数,其中
lambd
是分布的逆比例尺度参数。

import random

random_exponential
= random.expovariate(2) # 生成指数分布的随机数
print(
"指数分布的随机浮点数:", random_exponential)

14. random.gammavariate(alpha, beta)

random.gammavariate(alpha, beta)
函数生成一个服从Gamma分布的随机浮点数,其中
alpha
是形状参数,
beta
是尺度参数。

import random

random_gamma
= random.gammavariate(2, 1) # 生成Gamma分布的随机数
print(
"Gamma分布的随机浮点数:", random_gamma)

15. random.paretovariate(alpha)

random.paretovariate(alpha)
函数生成一个服从帕累托分布的随机浮点数,其中
alpha
是形状参数。

import random

random_pareto
= random.paretovariate(2) # 生成帕累托分布的随机数
print(
"帕累托分布的随机浮点数:", random_pareto)

通过理解这些分布的生成函数,可以更好地在统计建模、模拟实验等应用中使用
random
模块,满足不同分布的随机数需求。选择适当的分布和参数将有助于更准确地模拟实际情况。

16. random.weibullvariate(alpha, beta)

random.weibullvariate(alpha, beta)
函数生成一个服从威布尔分布的随机浮点数,其中
alpha
是形状参数,
beta
是尺度参数。

import random

random_weibull
= random.weibullvariate(2, 1) # 生成威布尔分布的随机数
print(
"威布尔分布的随机浮点数:", random_weibull)

17. random.gauss(mu, sigma)

random.gauss(mu, sigma)
函数生成一个服从高斯分布(正态分布)的随机浮点数,其中
mu
是均值,
sigma
是标准差。

import random

random_gaussian
= random.gauss(0, 1) # 生成高斯分布的随机数
print(
"高斯分布的随机浮点数:", random_gaussian)

18. random.lognormvariate(mu, sigma)

random.lognormvariate(mu, sigma)
函数生成一个服从对数正态分布的随机浮点数,其中
mu
是对数均值,
sigma
是对数标准差。

import random

random_lognormal
= random.lognormvariate(0, 1) # 生成对数正态分布的随机数
print(
"对数正态分布的随机浮点数:", random_lognormal)

19. random.vonmisesvariate(mu, kappa)

random.vonmisesvariate(mu, kappa)
函数生成一个服从von Mises分布(圆周分布)的随机浮点数,其中
mu
是分布的均值,
kappa
是分布的集中度参数。

import random

random_vonmises
= random.vonmisesvariate(0, 1) # 生成von Mises分布的随机数
print(
"von Mises分布的随机浮点数:", random_vonmises)

20. random.choices(population, weights=None, k=1)

random.choices(population, weights=None, k=1)
函数从总体
population
中以权重
weights
进行随机抽样,返回
k
个元素。

import random

choices_population
= [1, 2, 3, 4, 5]
weights
= [0.1, 0.2, 0.3, 0.2, 0.2]
random_choices
= random.choices(choices_population, weights=weights, k=3)
print(
"带权重的随机抽样结果:", random_choices)

这些函数覆盖了
random
模块中的主要随机数生成方法,可以满足多样化的需求。在实际应用中,根据具体场景选择适当的分布和函数,合理设置参数,能够更好地模拟真实情况,支持科学计算和数据分析。

总结

在本文中,我们深入探讨了Python标准库中的
random
模块,介绍了各种随机数生成函数以及它们的应用场景和代码示例。从生成均匀分布的
random.random()
到更复杂的分布如三角分布、Beta分布、威布尔分布等,
random
模块提供了丰富的工具来满足不同随机数需求。

我们学习了如何生成随机整数、随机浮点数,以及如何在序列中进行随机选择和打乱。探讨了种子的设置和伪随机数生成器的初始化,以及如何应用在实验重现和调试过程中。同时,通过深入了解各种分布的生成函数,我们能够更好地模拟和处理不同领域的实际问题。

最后,我们强调了在实际应用中,根据具体场景选择适当的随机数生成函数是非常关键的。合理设置参数,选择合适的分布,有助于提高模拟的准确性和实验的可重现性。
random
模块为科学计算、模拟实验和数据分析等领域提供了强大的工具,通过灵活应用这些函数,我们能够更好地处理各种随机性需求。

点击关注,第一时间了解华为云新鲜技术~

Qt 是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍如何运用
QNetworkAccessManager
组件实现Web网页访问。

QNetworkAccessManager是Qt网络模块中的关键类,用于管理网络访问和请求。作为一个网络请求的调度中心,它为Qt应用程序提供了发送和接收各种类型的网络请求的能力,包括常见的GET、POST、PUT、DELETE等。这个模块的核心功能在于通过处理
QNetworkReply

QNetworkRequest
来实现与网络资源的交互。

通过
QNetworkAccessManager
,Qt应用程序能够轻松地与远程服务器通信,获取数据或将数据上传到服务器。这种网络请求的管理不仅是异步的,以确保不会阻塞主线程,还提供了丰富的信号和槽机制,使得开发者可以灵活地处理不同阶段的网络操作。

通常,
QNetworkAccessManager
会与
QNetworkReply

QNetworkRequest
一起使用。
QNetworkRequest
用于封装和配置网络请求的各种属性,例如URL、请求头等。而
QNetworkReply
则代表了对网络请求的响应,包含了请求返回的数据和相关信息。这三者共同协作,为Qt应用程序提供了便捷、灵活且强大的网络通信能力。

1.1 通用API函数

1.1.1 QNetworkAccessManager

要想实现网络通信首先需要新建一个网络访问管理器,以下是
QNetworkAccessManager
类中的一些常用函数及其描述:

函数 描述
QNetworkAccessManager(QObject *parent = nullptr) 构造函数,创建一个
QNetworkAccessManager
实例。
virtual ~QNetworkAccessManager() 虚析构函数,释放
QNetworkAccessManager
实例。
QNetworkReply *get(const QNetworkRequest &request) 发送GET请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *post(const QNetworkRequest &request, QIODevice *data) 发送POST请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *post(const QNetworkRequest &request, const QByteArray &data) 发送POST请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *put(const QNetworkRequest &request, QIODevice *data) 发送PUT请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *put(const QNetworkRequest &request, const QByteArray &data) 发送PUT请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *deleteResource(const QNetworkRequest &request) 发送DELETE请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *head(const QNetworkRequest &request) 发送HEAD请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, QIODevice *data = nullptr) 发送自定义请求,并返回与请求关联的
QNetworkReply
对象。
QNetworkReply *sendCustomRequest(const QNetworkRequest &request, const QByteArray &verb, const QByteArray &data) 发送自定义请求,并返回与请求关联的
QNetworkReply
对象。
void setConfiguration(const QNetworkConfiguration &config) 设置网络配置,用于定制网络行为。
QNetworkConfiguration configuration() const 获取当前网络配置。
void clearAccessCache() 清除网络访问缓存。
void setCache(QAbstractNetworkCache *cache) 设置网络缓存。
QAbstractNetworkCache *cache() const 获取当前网络缓存。
void setCookieJar(QNetworkCookieJar *cookieJar) 设置用于管理HTTP cookie的
QNetworkCookieJar
QNetworkCookieJar *cookieJar() const 获取当前的HTTP cookie管理器。

这些函数提供了
QNetworkAccessManager
的核心功能,使得开发者能够方便地进行各种类型的网络请求,配置网络参数,并进行相关的网络管理操作。

1.1.2 QNetworkReply

以下是
QNetworkReply
类中的一些常用函数及其描述:

函数 描述
QByteArray readAll() const 读取所有可用的数据,并返回一个
QByteArray
,包含从网络回复读取的所有内容。
QByteArray peek(int maxSize) const 查看最多
maxSize
字节的可用数据,但不从缓冲区中移除。
QByteArray read(int maxSize) 从网络回复中读取最多
maxSize
字节的数据,并将其从缓冲区中移除。
QByteArray readLine(int maxSize = 0) 从网络回复中读取一行数据,最多包含
maxSize
字节,并将其从缓冲区中移除。
void ignoreSslErrors(const QList<QSslError> &errors = QList<QSslError>()) 忽略SSL错误,继续处理网络回复。
void abort() 终止网络回复的处理,关闭底层连接。
void close() 关闭网络回复的处理。
QUrl url() const 返回与网络回复相关联的URL。
QNetworkRequest request() const 返回生成此网络回复的网络请求。
QNetworkAccessManager *manager() const 返回与网络回复相关联的
QNetworkAccessManager
bool isFinished() const 检查网络回复是否已完成。
QNetworkReply::NetworkError error() const 返回网络回复的错误代码。
bool hasRawHeader(const QByteArray &headerName) const 检查网络回复是否包含指定原始头。
QList<QByteArray> rawHeaderList() const 返回网络回复的所有原始头的列表。
QByteArray rawHeader(const QByteArray &headerName) const 返回指定原始头的值。
QVariant header(QNetworkRequest::KnownHeaders header) const 返回指定标准头的值。
QList<QByteArray> rawHeaderValues(const QByteArray &headerName) const 返回指定原始头的所有值。
QVariant attribute(QNetworkRequest::Attribute code) const 返回指定网络请求属性的值。
QIODevice *readAllStandardOutput() 读取标准输出的所有数据,并返回一个
QIODevice
,用于访问读取的内容。
QIODevice *readAllStandardError() 读取标准错误的所有数据,并返回一个
QIODevice
,用于访问读取的内容。
bool isReadable() const 检查网络回复是否可读取。

这些函数提供了对
QNetworkReply
实例进行各种操作和查询的方法,包括读取回复数据、处理SSL错误、获取请求信息、检查错误状态等。开发者可以根据具体需求使用这些函数来有效地与网络回复进行交互。

1.1.3 QNetworkRequest

以下是
QNetworkRequest
类中的一些常用函数及其描述:

函数 描述
QNetworkRequest(const QUrl &url) 使用给定的URL构造一个
QNetworkRequest
实例。
void setUrl(const QUrl &url) 设置
QNetworkRequest
的URL。
QUrl url() const 返回与
QNetworkRequest
相关联的URL。
void setRawHeader(const QByteArray &headerName, const QByteArray &headerValue) 设置指定原始头的值。
QByteArray rawHeader(const QByteArray &headerName) const 返回指定原始头的值。
bool hasRawHeader(const QByteArray &headerName) const 检查
QNetworkRequest
是否包含指定原始头。
void setRawHeaderList(const QList<QByteArray> &headerList) 设置所有原始头的列表。
QList<QByteArray> rawHeaderList() const 返回
QNetworkRequest
的所有原始头的列表。
void setHeader(QNetworkRequest::KnownHeaders header, const QVariant &value) 设置指定标准头的值。
QVariant header(QNetworkRequest::KnownHeaders header) const 返回指定标准头的值。
void setAttribute(QNetworkRequest::Attribute code, const QVariant &value) 设置指定网络请求属性的值。
QVariant attribute(QNetworkRequest::Attribute code) const 返回指定网络请求属性的值。
void setSslConfiguration(const QSslConfiguration &config) 设置SSL配置。
QSslConfiguration sslConfiguration() const 返回SSL配置。
void setMaximumRedirectsAllowed(int maxRedirects) 设置允许的最大重定向次数。
int maximumRedirectsAllowed() const 返回允许的最大重定向次数。
void setOriginatingObject(QObject *object) 设置发起此网络请求的对象。
QObject *originatingObject() const 返回发起此网络请求的对象。
bool isEmpty() const 检查
QNetworkRequest
是否为空(未设置URL)。

这些函数提供了对
QNetworkRequest
实例进行各种操作和查询的方法,包括设置和获取头信息、设置SSL配置、设置和获取网络请求属性等。开发者可以根据具体需求使用这些函数来有效地构建和管理网络请求。

1.2 实现Web页面访问

要使用该模块读者应该在
*.pro
文件内包含
network
网络模块,并在头文件中引入
QNetworkAccessManager

QNetworkReply

QNetworkRequest
三个类,在建立访问时首先使用
QNetworkAccessManager
新增一个
manager
管理类,并通过
QNetworkRequest
类创建一个GET请求地址,通过使用
manager.get
方法实现对特定页面的访问。

当访问完成时需要通过一个信号来实现对数据的处理,在
QNetworkReply
类中包含有如下表所示的信号以供读者使用,例如当访问被完成时则自动触发
&QNetworkReply::finished
完成信号,此时只需要对该信号进行相应的处理即可,通常会使用一个槽函数来处理它。

信号 描述
finished() 当网络请求完成时发出。
downloadProgress(qint64, qint64) 在下载过程中定期发出,提供下载进度信息。参数为已下载的字节数和总字节数。
uploadProgress(qint64, qint64) 在上传过程中定期发出,提供上传进度信息。参数为已上传的字节数和总字节数。
readyRead() 当有可读取的数据时发出,用于通知应用程序可以调用
readAll()

read()
方法以获取更多数据。
error(QNetworkReply::NetworkError) 当网络请求发生错误时发出,参数为错误代码。
sslErrors(const QList<QSslError> &) 当SSL错误发生时发出,参数为SSL错误的列表。

这些信号提供了丰富的信息,使开发者能够在不同阶段处理网络请求。同理,在下载和上传过程中可以使用
downloadProgress

uploadProgress
信号来获取进度信息,
readyRead
信号表示有可读取的数据,
error
信号表示请求发生错误,
sslErrors
信号表示
SSL
相关的错误。

当信号被触发时则会通过
QObject::connect
连接到对应的槽函数上,如下案例中所示,在槽函数内通过
reply->attribute
方法我们获取到此次响应码中的
QNetworkRequest::HttpStatusCodeAttribute
属性,该属性用来指明本次访问的状态值。此类属性也有许多可供参考,如下所示;

属性 描述
QNetworkRequest::HttpStatusCodeAttribute HTTP响应的状态码。
QNetworkRequest::HttpReasonPhraseAttribute HTTP响应的原因短语,如"OK"、"Not Found"等。
QNetworkRequest::RedirectionTargetAttribute 重定向目标的URL。
QNetworkRequest::ConnectionEncryptedAttribute 连接是否加密的标志,返回一个
bool
值。
QNetworkRequest::SourceIsFromCacheAttribute 请求是否来自缓存的标志,返回一个
bool
值。
QNetworkRequest::HttpPipeliningAllowedAttribute 是否允许HTTP流水线传输的标志,返回一个
bool
值。
QNetworkRequest::HttpPipeliningWasUsedAttribute 是否使用了HTTP流水线传输的标志,返回一个
bool
值。
QNetworkRequest::CustomVerbAttribute 自定义请求动作(HTTP verb)的字符串。
QNetworkRequest::User 用户自定义的属性,用于存储任意类型的用户数据。

这些属性提供了额外的信息,使得开发者能够更全面地了解和处理网络响应。根据具体的应用需求,开发者可以选择使用这些属性中的一个或多个来获取所需的信息。

#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QDebug>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 创建网络访问管理器
    QNetworkAccessManager manager;

    // 创建GET请求
    QNetworkRequest request(QUrl("http://www.baidu.com"));

    // 发送GET请求
    QNetworkReply *reply = manager.get(request);

    // 连接信号槽,处理响应
    QObject::connect(reply, &QNetworkReply::finished, [&]()
    {
        if (reply->error() == QNetworkReply::NoError)
        {
            // 获取请求的 URL
            qDebug() << "Request URL:" << reply->request().url();

            // 输出请求头信息
            qDebug() << "Request Headers:";
            QList<QByteArray> requestHeaders = reply->request().rawHeaderList();
            foreach (const QByteArray &header, requestHeaders) {
                qDebug() << header << ":" << reply->request().rawHeader(header);
            }

            // 获取响应码
            int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
            qDebug() << "HttpStatusCodeAttribute:" << statusCode;

            // 连接是否加密的标志
            bool connectionEncryptedAttribute = reply->attribute(QNetworkRequest::ConnectionEncryptedAttribute).toBool();
            qDebug() << "ConnectionEncryptedAttribute:" << connectionEncryptedAttribute;

            // 请求是否来自缓存的标志
            bool sourceIsFromCacheAttribute = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
            qDebug() << "SourceIsFromCacheAttribute:" << sourceIsFromCacheAttribute;

            // HTTP请求是否被允许进行流水线处理的标志
            bool httpPipeliningAllowedAttribute = reply->attribute(QNetworkRequest::HttpPipeliningAllowedAttribute).toBool();
            qDebug() << "HttpPipeliningAllowedAttribute:" << httpPipeliningAllowedAttribute;

            // 输出响应头信息
            qDebug() << "Response Headers:";
            QList<QByteArray> responseHeaders = reply->rawHeaderList();
            foreach (const QByteArray &header, responseHeaders) {
                qDebug() << header << ":" << reply->rawHeader(header);
            }

            // 处理响应内容,这里可以使用 readAll() 方法获取响应内容
            // qDebug() << "Response Content:" << reply->readAll();
        } else
        {
            qDebug() << "Error:" << reply->errorString();
        }

        // 释放资源
        reply->deleteLater();
        QCoreApplication::quit();
    });

    return a.exec();
}

读者可自行编译并运行这段代码,观察请求与相应数据如下图所示;

至于如何在图形界面中使用则就更简单了,首先我们在
mainwindow.h
头文件中定义好所需要的两个槽函数,函数
on_finished()
用于在完成请求后被调用,函数
on_readyRead()
则用于在回调被执行后调用,并并以两个网络管理类的指针变量,如下所示;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    //自定义槽函数
    void on_finished();
    void on_readyRead();

    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    QNetworkAccessManager networkManager;   // 网络管理
    QNetworkReply *reply;                   // 网络响应
};

当获取按钮被点击后则开始执行读入指定URL地址,并对该地址进行网页访问,同时绑定这两个信号,一旦被触发则自动路由到对应的槽函数上面去,如下所示;

void MainWindow::on_pushButton_clicked()
{
    // 读入URL地址
    QString urlSpec = ui->lineEdit->text().trimmed();
    if (urlSpec.isEmpty())
    {
        QMessageBox::information(this, "错误", "请指定URL");
        return;
    }

    // 格式化URL
    QUrl newUrl = QUrl::fromUserInput(urlSpec);
    if (!newUrl.isValid())
    {
        QMessageBox::information(this, "错误", QString("无效URL: %1").arg(urlSpec));
        return;
    }

    // 访问页面
    reply = networkManager.get(QNetworkRequest(newUrl));

    // 完成时的槽函数绑定
    connect(reply, SIGNAL(finished()), this, SLOT(on_finished()));

    // 读入数据的槽函数绑定
    connect(reply, SIGNAL(readyRead()), this, SLOT(on_readyRead()));

}

相对应的,在
on_finished()
槽函数中我们将响应头读出并输出到文本框中,在
on_readyRead()
槽函数中则是对整个网站页面源代码的输出功能,完整代码如下所示;

void MainWindow::on_finished()
{
    // 获取响应码
    int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();

    if(statusCode == 200)
    {
        ui->plainTextEdit_2->appendPlainText("响应头数据:");
        // 输出响应头信息
        QList<QByteArray> responseHeaders = reply->rawHeaderList();
        foreach (const QByteArray &header, responseHeaders)
        {
            ui->plainTextEdit_2->appendPlainText(header + " : " + reply->rawHeader(header));
        }
    }
}

// 读入页面源代码
void MainWindow::on_readyRead()
{
    ui->plainTextEdit->setPlainText(reply->readAll());
}

运行代码,读者可自行输入特定的网站进行读取测试,如下所示(完整代码请参考课件部分);

转载至我的博客
https://www.infrastack.cn
,公众号:架构成长指南

在并发一致性控制场景中,我们常常用
for update
悲观锁来进行一致性的保证,但是如果不了解它的机制,就进行使用,很容易出现事故,比如
for update
进行了锁表导致其他请求只能等待,从而拖垮系统,因此了解它的原理是非常必要的,下面我们通过一系列示例进行测试,来看看到底是什么场景下锁表什么场景下锁行

验证

示例说明

创建一个账户表,插入基础数据,以
唯一索引

普通索引

主键

普通字段
4 个维度进行
select ... for update
查询,查看是进行锁表还是锁行

表创建

创建一个账户表,指定
account_no
为唯一索引、
id
为主键、
user_no
为普通字段、
curreny
为普通索引

CREATE TABLE `account_info` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT 'ID' ,
	`account_no` int NOT NULL COMMENT '账户编号',
	`user_no` varchar(32) NOT NULL COMMENT '用户 Id',
	`currency` varchar(10) NOT NULL COMMENT '币种',
  `amount` DECIMAL(10,2) NOT NULL COMMENT '金额',
	`freeze_amount` DECIMAL(10,2) NOT NULL COMMENT '冻结金额',
  `create_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间',
  `update_time` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '修改时间',
  PRIMARY KEY (`id`) USING BTREE,
	UNIQUE KEY `uni_idx_account_no` (`account_no`) ,
	KEY `idx_currency_` (`currency`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='账户信息表';

插入基础数据

insert into account_info values (1,1,'ur001','RMB',100,0,now(),now());
insert into account_info values (2,2,'ur002','RMB',1000,0,now(),now());
insert into account_info values (3,3,'ur002','DOLLAR',200,0,now(),now());

根据主键查询

在事务 1 中,根据主键id=1 进行 for update查询时,事务2、事务 3 都进行阻塞,而事务 4 由于更新的id=2 所以成功,因此判定,
根据主键进行 for update 查询时是行锁

根据唯一索引查询

在事务 1 中,根据唯一索引account_no=1 进行 for update查询时,事务2、事务 3 都进行阻塞,而事务 4 由于更新的account_no=2 所以成功,因此判定,
根据唯一索引进行 for update 查询时是行锁

根据普通索引查询

在事务 1 中,根据普通索引currency='RMB' 进行 for update查询时,事务2、事务 3 都进行阻塞,而事务 4 由于更新的currency='DOLLAR`所以成功,因此判定,
根据普通索引进行 for update 查询时是行锁

根据普通字段查询

在事务 1 中,根据普通字段user_no='ur001' 进行 for update查询时,事务2、事务 3 都进行阻塞,而事务 4查询的是user_no='ur002'也进行阻塞,因此判定,
根据普通字段进行 for update 查询时是表锁

总结

如果查询条件是索引/主键字段,那么
select ..... for update
会进行行锁

如果查询条件是普通字段(没有索引/主键),那么
select ..... for update
会进行锁表,这点一定要注意。

扫描下面的二维码关注我们的微信公众帐号,在微信公众帐号中回复◉加群◉即可加入到我们的技术讨论群里面共同学习。

在信息技术快速迭代的当下,.Net生态中的AntSK项目凭借其前沿的AI知识库和智能体技术,已经吸引了广大开发者的关注和参与。今天,我要给大家介绍的主角,AntSK 0.1.7版本,无疑将是这个开源项目中的一次重大进步——多模型管理功能的引入,为使用者带来了更强大、更灵活的工具。

AntSK简介

在我们深入探讨0.1.7版本的亮点之前,让我们先进行一个简单的复习:AntSK是什么?

AntSK是一个基于.Net平台和AntDesign Blazor框架开发的AI智能体与知识库项目。它依托Semantic Kernel的技术支持,旨在为开发者们提供一个强大、易于使用、可快速开发的AI智能体环境。通过这个项目,开发者可以更加高效地构建和管理智能对话系统,丰富应用程序的交互能力。

https://github.com/xuzeyu91/AntSK

多模型管理功能的新增

随着机器学习和人工智能的不断进步,AI模型多样化已成为一个不可逆转的趋势。为了适应这一趋势,AntSK 0.1.7版本充分利用了Semantic Kernel的强大功能,在系统中加入了模型配置功能。这意味着开发者可以根据需求,轻松配置和管理使用不同的模型,例如OpenAI和Azure OpenAI,或是LLamaSharp支持的本地GGUF模型等。这样的设计,不仅增强了AntSK的灵活性和适应性,也使得开发人员可以在不同应用中测试和比较不同模型的效果。

如何实现多模型管理?

有了新功能的介绍后,我们再来看看具体的实现方法,正是因为有了技术大咖
James Yeung
的代码贡献,我们才能如此顺利地实现这一创新。

在Semantic Kernel中,提供了一个

Services.AddKeyedSingleton<ITextGenerationService>

的方法,这是我们实现不同模型连接的关键。

我们以
TextCompletion
类的创建为例,这个类需要继承自
ITextGenerationService

IAIService
接口。在这个类中,我们需要实现两个关键的方法:
GetTextContentsAsync

GetStreamingTextContentsAsync
。通过这些实现,我们便完成了一个模型的连接器。

但这只是第一步,下一步我们需要在创建
Semantic Kernel
实例时,根据不同模型的类型加载对应的连接器。这一过程通过简单的
switch-case
实现:

var builder = Kernel.CreateBuilder();
 switch (chatModel.AIType)
 {
     case Model.Enum.AIType.OpenAI:
         builder.AddOpenAIChatCompletion(
            modelId: chatModel.ModelName,
            apiKey: chatModel.ModelKey,
            httpClient: chatHttpClient);
         break;
     case Model.Enum.AIType.AzureOpenAI:
         builder.AddAzureOpenAIChatCompletion(
             deploymentName: chatModel.ModelName,
             apiKey: chatModel.ModelKey,
             endpoint: chatModel.EndPoint
             );
         break;
     case Model.Enum.AIType.LLamaSharp:
         var (weights, parameters) = LLamaConfig.GetLLamaConfig(chatModel.ModelName);
         var ex = new StatelessExecutor(weights, parameters);
         builder.Services.AddKeyedSingleton<ITextGenerationService>("local-llama", new LLamaSharpTextCompletion(ex));
         break;
     case Model.Enum.AIType.SparkDesk:
         var options = new SparkDeskOptions { AppId = chatModel.EndPoint, ApiSecret = chatModel.ModelKey, ApiKey = chatModel.ModelName, ModelVersion= Sdcb.SparkDesk.ModelVersion.V3_5 };
         builder.Services.AddKeyedSingleton<ITextGenerationService>("spark-desk", new SparkDeskTextCompletion(options, app.Id));
         break;
 }

未来展望

通过这些简洁而强大的代码实现,AntSK的用户现在可以轻松切换和管理不同的AI模型了。当然,AI技术日新月异,AntSK的开发者们也必将不断迭代升级,以适应这一变化。我们可以期待,在不久的将来,AntSK能够支持更多的AI模型,甚至可以实现AI模型的实时学习与适配。

纵观如今的技术发展,AntSK成为其中的亮点,不仅证明了.Net社区的活力与创新能力,更预示着来自开源世界的明日之星,必将为我们的生活和工作带来更多的灵感和便利。

作为一名技术博主,我对AntSK项目的未来充满信心和期待。而我下一篇文章,可能就是介绍如何将
AntSK
的这一创新功能应用到一些具体的业务场景中。请期待并关注我的公众号,我将为你带来最前沿的技术动态和深入的技术解析。

结语

通过今天的分享,我希望你能对
AntSK
的最新版本有了更加深刻的理解,也希望可以激发起你的兴趣,一起参与到这个充满潜力的项目中来。记住,无论你是AI领域的专业人员,还是对人工智能充满好奇的初学者,
AntSK
项目都欢迎你的加入,和我们一起推动科技的进步。

记住,技术是为了解决问题而存在的。当你拥有了
AntSK
这样的工具时,你的技术梦想,也许就要成为现实了。

同时也欢迎大家加入我们的
.Net/AI应用开发交流群,可以关注我的公众号<许泽宇的技术分享>,发送进群

isolate-write-by-overlayfs.png

本文为从零开始写 Docker 系列第五篇,在
pivotRoot
基础上通过 overlayfs 实现写操作隔离,达到容器中写操作和宿主机互不影响。


完整代码见:
https://github.com/lixd/mydocker
欢迎 Star


推荐阅读以下文章对 docker 基本实现有一个大致认识:


开发环境如下:

root@mydocker:~# lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 20.04.2 LTS
Release:	20.04
Codename:	focal
root@mydocker:~# uname -r
5.4.0-74-generic


注意:需要使用 root 用户

1. 概述

上一篇中已经实现了使用宿主机 /root/busybox 目录作为容器的根目录,但在容器内对文件的操作仍然会直接影响到宿主机的 /root/busybox 目录。
本节要进一步进行容器和镜像隔离,实现在容器中进行的操作不会对镜像(宿主机/root/busybox目录)产生任何影响的功能

什么是 overlayfs?

overlayfs 是 UFS 的一种实现,UnionFS 全称为 Union File System ,是一种为 Linux FreeBSD NetBSD 操作系统设计的,
把其他文件系统联合到一个联合挂载点的文件系统服务

它使用 branch 不同文件系统的文件和目录“
透明地
”覆盖,形成一个单一一致的文件系统。

这些 branches 或者是 read-only 或者是 read-write 的,所以当对这个虚拟后的联合文件系统进行写操作的时候,系统是真正写到了一个新的文件中。看起来这个虚拟后的联合文件系统是可以对任何文件进行操作的,但是其实它并没有改变原来的文件,这是因为 unionfs 用到了一个重要的资管管理技术叫写时复制。

写时复制(copy-on-write,下文简称 CoW)
,也叫隐式共享,是一种对可修改资源实现高效复制的资源管理技术。

linux-ufs-edit

它的思想是,如果一个资源是重复的,但没有任何修改,这时候并不需要立即创建一个新的资源,这个资源可以被新旧实例共享。

创建新资源发生在第一次写操作,也就是对资源进行修改的时候。通过这种资源共享的方式,可以显著地减少未修改资源复制带来的消耗,但是也会在进行资源修改的时候增减小部分的开销。

UnionFS,最主要的功能是将多个不同位置的目录联合挂载(union mount)到同一个目录下。

比如,我现在有两个目录 A 和 B,它们分别有两个文件:

$ tree
.
├── A
│  ├── a
│  └── x
└── B
  ├── b
  └── x

然后,我使用联合挂载的方式,将这两个目录挂载到一个公共的目录 C 上:

$ mkdir C
$ mount -t aufs -o dirs=./A:./B none ./C

这时,我再查看目录 C 的内容,就能看到目录 A 和 B 下的文件被合并到了一起:

$ tree ./C
./C
├── a
├── b
└── x

可以看到,在这个合并后的目录 C 里,有 a、b、x 三个文件,并且 x 文件只有一份。这,就是“合并”的含义。

这就是联合文件系统,目的就是
将多个文件联合在一起成为一个统一的视图

UFS 有多种实现,例如 AUFS、Overlayfs 等,这里使用比较主流的 Overlayfs。

关于 Overlayfs 详细介绍可以看一下这篇文章:
Docker 魔法解密:探索 UnionFS 与 OverlayFS

里面详细介绍了 overlayfs 各个特性,以及 docker 中是如何使用 Overlayfs 的。

这里对需要用到部分做简要说明:

首先,overlayfs 一般分为 lower、upper、merged 和 work 4个目录。

  • lower 只读层,该层数据不会被修改
  • upper 可读写层,
    所有修改都发生在这一层,即使是修改的 lower 中的数据
  • merged 视图层,可以看到 lower、upper 中的所有内容
  • work 则是 overlayfs 内部使用

在本文实现中使用我们的镜像目录(busybox 目录) 作为 lower 目录,这样可以保证镜像内容部被修改。

merged 目录由于可以看到全部内容,因此作为容器 rootfs 目录,即 pivotRoot 会切换到 merged 目录。

upper 目录则是用于保存容器中的修改,因为 overlayfs 中所有修改都会发生在这里。


如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【
探索云原生
】即可订阅


2. Mount Overlayfs

Docker 在使用镜像启动一个容器时,会新建2个layer: write layer和 container-init layer。

write layer是容器唯一的 可读写层;而 container-init layer 是为容器新建的只读层,用来存储容器启动时传入的系统信息(不过在实际的场景下,它们并不是以write layer和container-init layer命名的)。最后把write layer、container-init layer 和相关镜像的 layers 都 mount 到一个 mnt 目录下,然后把这个 mnt 目录作为容器启动的根目录。

同样的,我们在容器启动前,也需要先 mount 好 overlayfs 目录,然后执行 privotRoot 时直接切换到 mount 好的 overlayfs merge 目录即可。

NewWorkSpace 函数是用来创建容器文件系统的,它包括 createLower、createDirs和mountOverlayFS。
分为以下步骤:

  • 1)准备 busybox 目录,之前都是手动解压准备 /root/busybox 目录,这次把解压逻辑加入到代码中。只需要准备好 busybox.tar 文件即可。容器启动时自动将 busybox.tar 解压到 busybox 目录下,作为容器的只读层。

  • 2)准备 overlayfs 目录,创建好挂载 overlayfs 需要的 upper、work 和 merged 目录

  • 3)实现 mount overlayfs,将 merged 目录作为挂载点,然后把 busybox、upper 挂载到 merged 目录。

  • 4)更新 pivotRoot 调用目录,将 rootfs 从宿主机目录 root/busybox 切换到上一步中挂载的/root/merged 目录

  • 最后 NewParentProcess 函数中将容器使用的宿主机目录 root/busybox 替换成/root/merged。

// NewWorkSpace Create an Overlay2 filesystem as container root workspace
func NewWorkSpace(rootPath string) {
	createLower(rootPath)
	createDirs(rootPath)
	mountOverlayFS(rootPath)
}

// createLower 将busybox作为overlayfs的lower层
func createLower(rootURL string) {
	// 把busybox作为overlayfs中的lower层
	busyboxURL := rootURL + "busybox/"
	busyboxTarURL := rootURL + "busybox.tar"
	// 检查是否已经存在busybox文件夹
	exist, err := PathExists(busyboxURL)
	if err != nil {
		log.Infof("Fail to judge whether dir %s exists. %v", busyboxURL, err)
	}
	// 不存在则创建目录并将busybox.tar解压到busybox文件夹中
	if !exist {
		if err := os.Mkdir(busyboxURL, 0777); err != nil {
			log.Errorf("Mkdir dir %s error. %v", busyboxURL, err)
		}
		if _, err := exec.Command("tar", "-xvf", busyboxTarURL, "-C", busyboxURL).CombinedOutput(); err != nil {
			log.Errorf("Untar dir %s error %v", busyboxURL, err)
		}
	}
}

// createDirs 创建overlayfs需要的的upper、worker目录
func createDirs(rootURL string) {
	upperURL := rootURL + "upper/"
	if err := os.Mkdir(upperURL, 0777); err != nil {
		log.Errorf("mkdir dir %s error. %v", upperURL, err)
	}
	workURL := rootURL + "work/"
	if err := os.Mkdir(workURL, 0777); err != nil {
		log.Errorf("mkdir dir %s error. %v", workURL, err)
	}
}

// mountOverlayFS 挂载overlayfs
func mountOverlayFS(rootURL string, mntURL string) {
	// mount -t overlay overlay -o lowerdir=lower1:lower2:lower3,upperdir=upper,workdir=work merged
	// 创建对应的挂载目录
	if err := os.Mkdir(mntURL, 0777); err != nil {
		log.Errorf("Mkdir dir %s error. %v", mntURL, err)
	}
	// 拼接参数
	// e.g. lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/merged
	dirs := "lowerdir=" + rootURL + "busybox" + ",upperdir=" + rootURL + "upper" + ",workdir=" + rootURL + "work"
	// dirs := "dirs=" + rootURL + "writeLayer:" + rootURL + "busybox"
	cmd := exec.Command("mount", "-t", "overlay", "overlay", "-o", dirs, mntURL)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("%v", err)
	}
}

接下来,在 NewParentProcess 函数中将容器使用的宿主机目录/root/busybox 替换成root/mnt 。这样 ,使用 OverlayFS 系统启动容器的代码就完成了。

func NewParentProcess(tty bool) (*exec.Cmd, *os.File) {
	// 省略其他代码
	cmd.ExtraFiles = []*os.File{readPipe}
	mntURL := "/root/merged/"
	rootURL := "/root/"
	NewWorkSpace(rootURL, mntURL)
	cmd.Dir = mntURL
	return cmd, writePipe
}

3. Unmount Overlayfs

Docker 会在删除容器的时候,把容器对应 WriteLayer 和 Container-init Layer 删除,而保留镜像所有的内容。本节中在容器退出的时候也会删除 upper、work 和 merged 目录只保留作为镜像的 lower 层目录即 busybox。

具体步骤如下:

  • 1)unmount overlayfs:将/root/merged目录挂载解除
  • 2)删除其他目录:删除之前为 overlayfs 准备的 upper、work、merged 目录

由于 overlayfs 的特性,所有修改操作都发生在 upper 目录,因此目录删除后容器对文件系统的更改,就都已经抹去了。

DeleteWorkSpace 函数包括 umountOverlayFS 和 deleteDirs。

// DeleteWorkSpace Delete the AUFS filesystem while container exit
func DeleteWorkSpace(rootURL string, mntURL string) {
	umountOverlayFS(mntURL)
	deleteDirs(rootURL)
}

func umountOverlayFS(mntURL string) {
	cmd := exec.Command("umount", mntURL)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		log.Errorf("%v", err)
	}
	if err := os.RemoveAll(mntURL); err != nil {
		log.Errorf("Remove dir %s error %v", mntURL, err)
	}
}

func deleteDirs(rootURL string) {
	writeURL := rootURL + "upper/"
	if err := os.RemoveAll(writeURL); err != nil {
		log.Errorf("Remove dir %s error %v", writeURL, err)
	}
	workURL := rootURL + "work"
	if err := os.RemoveAll(workURL); err != nil {
		log.Errorf("Remove dir %s error %v", workURL, err)
	}
}

4. 测试

首先将
busybox.tar
放到 /root 目录下:

$ ls
busybox.tar

然后启动我们的容器

root@mydocker:~/feat-overlayfs/mydocker# ./mydocker run -it /bin/sh
{"level":"info","msg":"resConf:\u0026{ 0  }","time":"2024-01-16T13:36:38+08:00"}
{"level":"info","msg":"enter NewWorkSpace","time":"2024-01-16T13:36:38+08:00"}
{"level":"info","msg":"enter createLower","time":"2024-01-16T13:36:38+08:00"}
{"level":"info","msg":"busybox:/root/busybox busybox.tar:/root/busybox.tar","time":"2024-01-16T13:36:38+08:00"}

再次查看宿主机的 /root 目录:

root@mydocker:~# ls /root
busybox  busybox.tar  merged  upper  work

可以看到,多了几个目录:busybox、merged、upper、work。

在容器中新建一个文件:

/ # echo KubeExplorer > tmp/hello.txt
/ # ls /tmp
hello.txt
/ # cat /tmp/hello.txt
KubeExplorer

然后切换到宿主机:

root@mydocker:~# ls busybox/tmp
root@mydocker:~# ls upper/tmp
hello.txt
root@mydocker:~# ls merged/tmp
hello.txt

可以发现,这个新创建的文件居然不在 busybox 目录,而是在 upper 中,然后 merged 目录中也可以看到。

这就是 overlayfs 的作用了。

写操作不会修改 lower 目录(busybox),而是发生在 upper 中,即在 upper 中 tmp 目录并创建了 hello.txt 文件。

而 merged 作为挂载点自然是能够看到 hello.txt 文件的。

最后在容器中执行 exit 退出容器。

/ # exit

然后再次查看宿主机上的 root 文件夹内容。

root@mydocker:~# ls /root
busybox  busybox.tar

可以看到,upper、work 和 merged 目录被删除,作为镜像的 busybox 层仍然保留。

并且 busybox 中的内容未被修改:

root@mydocker:~# ls /root/busybox
bin  dev  etc  home  proc  root  sys  tmp  usr  var

至此,基本实现了 Docker 的效果:

  • 1)镜像中的文件不会被修改
  • 2)容器中的修改不会影响宿主机
  • 3)容器退出后,修改内容丢失

5. 小结

overlayfs 引入具体流程如下:

  • 1)自动解压 busybox.tar 到 busybox 作为 lower 目录,类似 docker 镜像层
  • 2)容器启动前准备好 lower、upper、work、merged 目录并 mount 到 merged 目录
  • 3)容器启动后使用 pivotRoot 将 rootfs 切换到 merged 目录
    • 后续容器中的修改由于 overlayfs 的特性,都会发生在 upper 目录中,而不会影响到 lower 目录
  • 4)容器停止后 umount 并移除upper、work、merged 目录




如果你对云原生技术充满好奇,想要深入了解更多相关的文章和资讯,欢迎关注微信公众号。

搜索公众号【
探索云原生
】即可订阅


最后在推荐一下
Docker 魔法解密:探索 UnionFS 与 OverlayFS


完整代码见:
https://github.com/lixd/mydocker
欢迎 Star

相关代码见
feat-overlayfs
分支,测试脚本如下:

需要提前在 /root 目录准备好 busybox.tar 文件,具体看上一篇文章第二节。

# 克隆代码
git clone -b feat-overlayfs https://github.com/lixd/mydocker.git
cd mydocker
# 拉取依赖并编译
go mod tidy
go build .
# 测试 查看文件系统是否变化
./mydocker run -it  /bin/ls