MPI4.1文档5-语言绑定、进程、错误处理、progress和实现

本博客参考官方文档进行介绍,全网仅此一家进行中文翻译,走过路过不要错过。

官方网址:https://www.mpi-forum.org/

参考文档:https://www.mpi-forum.org/docs/mpi-4.1/mpi41-report.pdf

引用官方4.1文档方法:

@manual{mpi41,
  author = "{Message Passing Interface Forum}",
  title  = "{MPI}: A Message-Passing Interface Standard Version 4.1",
  url   = "https://www.mpi-forum.org/docs/mpi-4.1/mpi41-report.pdf",
  year  = 2023,
  month  = nov
}

本文档分成多个博客进行介绍,在本人专栏中含有所有内容:

https://blog.csdn.net/qq_33345365/category_12610893.html

MPI-4.1为2023年11月2日发表,本专栏书写日期2024/3/18

本人会维护一个总版本,一个小章节的版本,总版本会持续更新,小版本会及时的调整错误和不合理的翻译,内容大部分使用chatGPT 3.5翻译,内容不引用文献,请自行去文档查看


开始编辑时间:2024/3/18;最后编辑时间:2024/3/22

2.6 语言绑定 Language Binding

这个部分定义了MPI语言绑定的规则,特别是对Fortran和ISO C的规定。(请注意,ANSI C已被ISO C取代。)这里定义了各种对象表示方式,以及用于表达该标准的命名约定。实际的调用序列在其他地方定义。

MPI绑定适用于Fortran 90或更新版本,尽管最初设计是为了在Fortran 77环境中使用。使用mpi_f08模块时,还需要两个新的Fortran特性,即假定类型(即TYPE(*))和假定rank(即DIMENSION(…)),详见第2.5.5节。

由于“PARAMETER”是Fortran语言中的关键字,我们使用“argument”一词来表示子例程的参数。这些参数在C中通常称为参数,但我们期望C程序员能理解“argument”这个词(在C中没有具体的含义),从而避免对Fortran程序员造成不必要的困惑。

由于Fortran是不区分大小写的,链接器在解析Fortran名称时可能会使用小写或大写。使用区分大小写语言的用户应避免使用“MPI_”和“PMPI_”形式的任何前缀,其中任何字母都可以是大写或小写。

2.6.1 已弃用和移除的接口 Deprecated and Removed Interfaces

一些章节提到了被弃用或替换的MPI构造。这些构造仍然是MPI标准的一部分,如第16章所述,但建议用户不要继续使用,因为较新版本的MPI提供了更好的解决方案。例如,MPI-1函数的Fortran绑定使用INTEGER作为地址参数,这与C绑定不一致,在具有32位INTEGER和64位地址的机器上会导致问题。在MPI-2中,这些函数被赋予了新名称,并为地址参数提供了新的绑定。使用旧函数被声明为不推荐使用。为了保持一致性,在这里和其他几个情况下,即使新函数等效于旧函数,也提供了新的C函数。旧名称已被弃用。

一些之前弃用的构造现在已被移除,如第17章所述。它们可能仍由实现提供以保持向后兼容性,但不是必需的。

表2.1列出了所有被弃用和移除的构造列表。请注意,此列表中包括了一些C typedefs和Fortran子程序名称;它们是回调函数的类型。

2.6.2 Fortran绑定事务 Fortran Binding Issues

MPI-1.1最初提供了对Fortran 77的绑定。这些绑定仍然保留,但现在在Fortran 90标准的背景下进行解释。MPI仍然可以与大多数Fortran 77编译器一起使用,如下所述。当使用术语“Fortran”时,指的是Fortran 90或更新版本;如果使用了mpi_f08模块,则指的是Fortran 2008与TS 29113,后者现在已成为Fortran 2018及更高版本的组成部分。

所有Fortran MPI名称都具有MPI_前缀。虽然Fortran不区分大小写,但如果使用了mpi_f08模块,则MPI_前缀后的第一个字符是大写,其余字符均为小写。如果没有使用mpi_f08模块,则所有字符均为大写。程序不能声明以MPI_前缀开头的名称,例如变量、子程序、函数、参数、派生类型、抽象接口或模块。为了避免与分析接口冲突,程序还必须避免具有PMPI_前缀的子程序和函数。这是为了避免可能的名称冲突。

所有MPI Fortran子程序的最后一个参数是错误代码。使用USE mpi_f08时,此最后一个参数声明为可选的,但用户定义的回调函数(例如,COMM_COPY_ATTR_FUNCTION)及其预定义的回调(例如,MPI_COMM_NULL_COPY_FN)除外。一些作为函数的MPI操作没有错误代码参数。成功完成的错误代码值为MPI_SUCCESS。其他错误代码是实现相关的;请参阅第9章和附录A中的错误代码。

表示字符串最大长度的常量在Fortran中比在C中小一个,如第19.3.9节所述。

在Fortran中,句柄表示为INTEGER,或者使用mpi_f08模块的BIND©派生类型;请参阅第2.5.1节。二进制变量的类型为LOGICAL。

数组参数的索引从1开始。

旧版MPI Fortran绑定——使用mpi和(不推荐使用的)mpif.h——在几个方面与Fortran标准不一致。这些不一致性,如寄存器优化问题,对用户代码有影响,详细讨论见第19.1.16节。

在Fortran中,仅在使用较新的MPI Fortran绑定(USE mpi_f08)时才支持大计数和位移。为了提高可读性,所有Fortran大计数过程声明都用注释“!(_c)”标记。

表 2.1 已弃用和移除的构造:请自行去网页查看

2.6.3 C绑定事务 C Binding Issues

我们使用ISO C声明格式。所有MPI名称都有MPI_前缀,定义的常量均为全大写,而定义的类型和函数在前缀后有一个大写字母。程序不得声明任何以MPI_为前缀的名称(标识符),例如变量、函数、常量、类型或宏,其中任何一个字母都可以是大写或小写。为了支持性能分析接口,程序不得声明以PMPI_为前缀的名称的函数,其中任何一个字母都可以是大写或小写。

对于命名常量、函数原型和类型定义,必须提供一个名为mpi.h的包含文件。

几乎所有C函数都会返回一个错误代码。成功的返回值将是MPI_SUCCESS,但在失败后引发的错误代码是与实现相关的。

对于每个类别的不透明对象的句柄,都提供了类型声明。

数组参数从零开始索引。

逻辑标志是整数,值为0表示“false”,非零值表示“true”。

选择参数是void*类型的指针。

2.6.4 函数和宏

实现允许将MPI_AINT_ADD、PMPI_AINT_ADD、MPI_AINT_DIFF和PMPI_AINT_DIFF作为宏在C语言中实现,而不允许实现其他内容。

Advice to implementors. 实现者应该记录哪些例程被实现为宏。(End of advice to implementors.)

Advice to users. 如果这些例程被实现为宏,它们将无法与MPI性能分析接口一起工作。(End of advice to users.)

2.7 进程 Processes

MPI程序由执行自己代码的自治进程组成,采用MIMD(多指令多数据)风格。每个进程执行的代码不必相同。进程通过调用MPI通信原语进行通信。通常情况下,每个进程在自己的地址空间中执行,尽管也可以实现共享内存的MPI。

该文档规定了并行程序的行为,假设仅使用MPI调用。MPI程序与其他可能的通信、I/O和进程管理方式的交互未予规定。除非在标准规范中另有规定,否则MPI不对其与提供类似或等效功能的外部机制的交互结果提出要求。这包括但不限于,与外部进程控制机制、共享和远程内存访问、文件系统访问和控制、进程间通信、进程信号和终端I/O的交互。高质量的实现应努力使这些交互的结果对用户直观,并在必要时尽可能地记录限制。

Advice to implementors. 对于支持MPI中附加机制的功能的实现,预期应该记录这些机制如何与MPI进行交互。(End of advice to implementors.)

MPI和线程的交互在第11.6节中定义。

如果可以在MPI进程之间共享一段内存(即使通过加载/存储访问,使一段内存(共享内存段)同时从所有这些MPI进程访问),则MPI进程驻留在同一共享内存域中。 对于属于多个共享内存域的进程组,创建属于同一共享内存域的进程子组在第7.4.2节中定义。

2.8 错误处理 Error Handling

MPI提供给用户可靠的消息传输。发送的消息始终正确接收,用户无需检查传输错误、超时或其他错误条件。换句话说,MPI不提供处理通信系统传输失败的机制。如果MPI实现建立在不可靠的底层机制上,那么MPI子系统的实现者的任务是保护用户免受这种不可靠性,并仅反映不可恢复的传输失败。尽可能地,这些失败将作为相关通信调用中的错误反映出来。

同样地,MPI本身不提供处理MPI进程失败的机制,即当MPI进程意外并永久停止通信时(例如,软件或硬件崩溃导致MPI进程意外终止)。

当然,MPI程序仍然可能有错误。当使用不正确的参数进行MPI调用时(发送操作中的不存在的目标,接收操作中的缓冲区太小等),程序错误可能会发生。这种类型的错误在任何实现中都会发生。此外,资源错误可能会在程序超出可用系统资源的数量时发生(挂起消息数量、系统缓冲区等)。这种类型的错误发生取决于系统中可用资源的数量和资源分配机制的使用情况;这可能因系统而异。高质量的实现将为重要资源提供宽松的限制,以减轻这种可移植性问题。

在C和Fortran中,几乎所有MPI调用都返回一个代码,指示操作成功完成。在可能的情况下,如果在调用期间发生错误,则MPI调用会返回一个错误代码。默认情况下,在MPI库的执行期间检测到错误将导致并行计算中止,除了文件操作。然而,MPI提供了机制让用户更改此默认行为,并处理可恢复的错误。用户可以指定没有错误是致命的,并自行处理MPI调用返回的错误代码。此外,用户可以提供用户定义的错误处理程序,当MPI调用异常返回时将调用这些程序。MPI的错误处理设施在第9.3节中进行了描述。

MPI调用返回有意义的错误代码时受到多种因素的限制。当出现错误时,MPI可能无法检测到某些错误;其他错误在正常执行模式下可能成本过高以至于无法检测到;一些故障(例如内存故障)可能会破坏MPI库及其输出的状态;最后,一些错误可能是“灾难性的”,可能会阻止MPI将控制权返回给调用者。

此外,一些错误可能在不涉及可以获取关联错误处理程序的MPI对象的操作中被检测到。错误处理程序的关联在第9.3节中进一步描述。在这种情况下,这些错误将在使用世界模型时(参见第11.2节)引发在通信器MPI_COMM_SELF上。当MPI_COMM_SELF未初始化(即在MPI_INIT / MPI_INIT_THREAD之前,在MPI_FINALIZE之后,或者仅使用Sessions模型时)时,错误将引发初始错误处理程序(在启动操作期间设置,请参阅11.8.4节)。Sessions模型在第11.3节中描述。

最后,一些错误可能在关联操作在本地完成后被检测到。这种情况的一个示例是由于异步通信的性质而引起的:MPI调用可能会启动在调用返回后继续异步进行的操作。因此,操作可能会以指示成功完成的代码返回,但随后可能引发错误。如果有后续调用与同一操作相关(例如,验证异步操作是否已完成的调用),那么与此调用相关联的错误参数将用于指示错误的性质。在少数情况下,错误可能会在与操作相关的所有调用都已返回之后发生,因此无法使用错误值来指示错误的性质(例如,在发送具有准备模式的接收器中的错误程序时)。

此文档不会指定发生错误的MPI调用后计算的状态。期望的行为是返回相关的错误代码,并将错误的影响尽可能局限在最大范围内。例如,极度希望错误的接收调用不会导致接收方内存的任何部分被覆盖,超出接收消息指定区域的范围。 在支持在此处定义为错误的MPI调用方面,实现可以超越此文档。例如,MPI在匹配发送和接收操作之间指定了严格的类型匹配规则:发送浮点变量并接收整数是错误的。实现可以超越这些类型匹配规则,并在此类情况下提供自动类型转换。对于这种不符合规范行为,生成警告将是有帮助的。

MPI定义了一种让用户创建新错误代码的方法,如第9.5节所述。

2.9 Progress

MPI通信操作或并行I/O模式通常包括在一个或多个MPI进程中执行的几个相关操作。例如,点对点通信中一个MPI进程执行发送操作,另一个(或同一个)MPI进程执行接收操作,或者一个组中的所有MPI进程执行集合操作。

在每个MPI进程中,通信或并行I/O模式的部分在属于该操作的MPI过程调用中执行,而其他部分则是分离的MPI活动,即它们可以在额外的进展线程中执行,转移到网络接口控制器(NIC),或者在与给定通信或并行I/O模式语义不相关的其他MPI过程调用中执行。

如果MPI过程调用被阻塞,那么它会延迟返回,直到另一个MPI进程中发生了特定的活动或状态变化。

  • 一个非本地的MPI过程调用,它延迟返回直到另一个MPI进程上的某个特定语义相关的MPI调用完成,或者
  • 一个本地的MPI过程调用,它延迟返回直到另一个MPI进程上的某个不特定的MPI调用导致该进程的特定状态变化,或者
  • 一个MPI终止过程(MPI_FINALIZE或MPI_SESSION_FINALIZE),它延迟返回或退出,因为这个MPI终止必须保证在调用MPI进程中与该MPI终止调用相关的所有分离的MPI活动在这个MPI终止完成之前被执行。注意,MPI终止过程可能在最终化之前执行属性删除回调函数(参见第11.2.4节);这些回调函数可能会生成额外的分离MPI活动。

非本地阻塞 MPI 过程调用的一些示例:

  • MPI_SSEND 延迟返回,直到目标 MPI 进程启动匹配的接收操作(例如通过调用 MPI_RECV 或 MPI_IRECV)。
  • MPI_RECV 延迟返回,直到源 MPI 进程启动匹配的发送操作(例如通过调用 MPI_SEND 或 MPI_ISEND)。

本地阻塞 MPI 过程调用的一些示例:

  • 如果消息数据无法完全缓冲,MPI_RSEND 延迟返回,直到目标 MPI 进程接收了无法缓冲的消息数据部分,这可能需要一个或多个不特定的 MPI 过程调用在目标 MPI 进程中执行。
  • 如果消息在发送 MPI 进程处被缓冲(例如使用 MPI_BSEND),MPI_RECV 延迟返回,直到消息被接收,这可能需要一个或多个不特定的 MPI 过程调用在发送 MPI 进程中发送缓冲数据。

所有 MPI 进程都需要保证progress,即所有分离的 MPI 活动最终会执行。此保证需要在以下情况下提供:

  • 阻塞的 MPI 过程,以及
  • 重复调用的 MPI 测试过程(见下文),返回 flag=false。

进度必须独立于MPI活动是否属于特定会话或世界模型(参见第11.2节和11.3节)进行提供。实现此保证的其他方法也是可能的和允许的(例如,专用进度线程或将其卸载到网络接口控制器(NIC))。

MPI测试程序包括MPI_TEST、MPI_TESTANY、MPI_TESTALL、MPI_TESTSOME、MPI_IPROBE、MPI_IMPROBE、MPI_REQUEST_GET_STATUS、MPI_WIN_TEST和MPI_PARRIVED。

如果所有本地程序都独立于其他MPI进程中的MPI过程调用(与操作相关或不相关)而返回,则MPI实现将提供强进度。如果MPI实现没有提供强进度,则提供弱进度。

Advice to users. MPI操作的性能可能会受到进展类型的影响。一个正确的MPI应用程序必须在假设仅提供弱进展的情况下编写。每个在弱进展下正确的MPI应用程序,如果提供了强进展,都将被正确执行。此外,MPI标准的设计是这样的,如果在提供强进展的情况下正确性成立,那么即使实现仅提供弱进展,也应该正确执行。(End of advice to users.)

Rationale. MPI在使用非基于MPI程序的同步方法时不能保证进展。在MPI中没有强进展的保证可能会导致死锁,可以参考第2.7节和第12.7.3节中的示例12.13。(End of rationale.)

请查阅第2.4.2节中关于本地MPI过程的定义,并查看总索引中所有关于进展的引用。

2.10 实现问题 Implementation Issues

MPI实现可能与操作环境和系统的许多领域进行交互。虽然MPI并不要求提供任何服务(如信号处理),但如果这些服务可用,它确实强烈建议提供这些服务的行为。这是实现跨平台可移植性的重要点,前提是提供相同服务集的平台。

2.10.1 基本运行时例程的独立性 Independence of Basic Runtime Routines

MPI程序要求基本语言环境中的库例程(例如Fortran中的write,ISO C中的printf和malloc)在MPI_INIT之后并在MPI_FINALIZE之前独立运行,并且它们的完成与MPI程序中其他进程的操作无关。

请注意,这并不妨碍创建提供并行服务且其操作是集体的库例程。然而,以下程序在ISO C环境中应该能够完成,而不管MPI_COMM_WORLD的大小(假设printf在执行MPI进程中可用)。

int commworld_rank;
MPI_Init (( void *)0 , ( void *)0);
MPI_Comm_rank ( MPI_COMM_WORLD , & commworld_rank );
if (commworld_rank == 0) printf (" Starting program \n");
MPI_Finalize ();

对应的Fortran程序也应该完成。

不需要的是这些例程在被多个MPI进程调用时的具体顺序。例如,MPI对以下程序的输出既不要求也不建议(假设在执行MPI进程中可以进行I/O操作)。

MPI_Comm_rank ( MPI_COMM_WORLD, & commworld_rank);
printf (" Output from MPI process where commworld_rank =%d\n",
commworld_rank);

此外,由于资源耗尽或其他错误而导致的调用失败不被视为违反此处的要求(然而,它们必须完成,只是不必成功完成)。

2.10.2 与信号的交互 Interaction with Signals

MPI不指定进程与信号的交互,并且不要求MPI是信号安全的。实现可以保留一些信号供其自身使用。要求实现文档记录使用的信号,并强烈建议不要使用SIGALRM、SIGFPE或SIGIO。实现还可以禁止在信号处理程序中使用MPI调用。

在多线程环境中,用户可以通过仅在不执行MPI调用的线程上捕获信号来避免信号与MPI库之间的冲突。高质量的单线程实现将是信号安全的:由信号挂起的MPI调用将在处理信号后正常恢复并完成。

2.11 例子

这份文档中的示例仅用于说明目的,不旨在规定标准。其中许多示例是由从MPI标准的源文件中提取示例的工具编译而成的。然而,这些示例并未经过仔细检查或验证。

  • 29
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
好的,以下是一个简单的使用MPI实现矩阵-向量乘法的示例代码。在这个例子中,我们假设矩阵和向量都是由进程0输入的。 ```c #include <stdio.h> #include <stdlib.h> #include <mpi.h> #define ROWS 4 #define COLS 4 int main(int argc, char** argv) { int rank, size, i, j; int matrix[ROWS][COLS], vector[COLS], result[ROWS], partial_result[ROWS]; MPI_Status status; MPI_Init(&argc, &argv); MPI_Comm_rank(MPI_COMM_WORLD, &rank); MPI_Comm_size(MPI_COMM_WORLD, &size); if(rank == 0) { // 进程0读取矩阵和向量 printf("Enter the matrix (%d x %d):\n", ROWS, COLS); for(i = 0; i < ROWS; i++) { for(j = 0; j < COLS; j++) { scanf("%d", &matrix[i][j]); } } printf("Enter the vector (%d elements):\n", COLS); for(i = 0; i < COLS; i++) { scanf("%d", &vector[i]); } } // 广播向量到所有进程 MPI_Bcast(vector, COLS, MPI_INT, 0, MPI_COMM_WORLD); // 将矩阵分成若干行,并分配给各个进程 int rows_per_process = ROWS / size; int start_row = rank * rows_per_process; int end_row = (rank == size - 1) ? ROWS : start_row + rows_per_process; int num_rows = end_row - start_row; // 发送各个进程需要计算的矩阵行 if(rank == 0) { for(i = 1; i < size; i++) { MPI_Send(&matrix[i * rows_per_process][0], rows_per_process * COLS, MPI_INT, i, 0, MPI_COMM_WORLD); } } else { MPI_Recv(matrix, num_rows * COLS, MPI_INT, 0, 0, MPI_COMM_WORLD, &status); } // 计算每个进程的部分结果 for(i = 0; i < num_rows; i++) { partial_result[i] = 0; for(j = 0; j < COLS; j++) { partial_result[i] += matrix[i + start_row][j] * vector[j]; } } // 将每个进程的部分结果发送给进程0 if(rank == 0) { for(i = 0; i < num_rows; i++) { result[i] = partial_result[i]; } for(i = 1; i < size; i++) { MPI_Recv(&result[i * rows_per_process], rows_per_process, MPI_INT, i, 0, MPI_COMM_WORLD, &status); } } else { MPI_Send(partial_result, num_rows, MPI_INT, 0, 0, MPI_COMM_WORLD); } // 进程0输出结果 if(rank == 0) { printf("Result:\n"); for(i = 0; i < ROWS; i++) { printf("%d\n", result[i]); } } MPI_Finalize(); return 0; } ``` 在这个代码中,我们使用了MPI的广播和发送/接收操作来将矩阵和向量分发给各个进程,并将每个进程的部分结果发送回进程0进行汇总。在实际使用中,您可能需要根据需要调整代码以适应您的特定情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

whyte王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值