17、Go语言中的并发与数据IO

Go语言中的并发与数据IO

1. Go语言并发编程

在Go语言并发编程中,有几个重要的概念和技术值得深入探讨。

1.1 通道选择与超时

在Go语言中, select 语句常被用于处理多个通道操作。在之前的代码片段中, words 生成器函数会选择第一个成功的通信操作: out <- word <-stopCh 。只要 main() 函数中的消费者代码继续从 out 通道接收数据,发送操作就会首先成功。不过,当 main() 函数遇到第三个 “the” 实例时,它会关闭 stopCh 通道,此时 select 语句中的接收操作会优先执行,从而使 goroutine 返回。

通道超时是Go并发编程中常见的一种模式,可通过 select 语句结合 time 包的API来实现。以下代码展示了一个单词直方图示例,如果程序统计和打印单词的时间超过200微秒,就会超时:

func main() {
   data := []string{...}
   histogram := make(map[string]int)
   done := make(chan struct{})
   go func() {
         defer close(done)
         words := words(data) // returns handle to channel
         for word := range words {
               histogram[word]++
         }
         for k, v := range histogram {
               fmt.Printf("%s\t(%d)\n", k, v)
         }
   }()
   select {
   case <-done:
         fmt.Println("Done counting words!!!!")
   case <-time.After(200 * time.Microsecond):
         fmt.Println("Sorry, took too long to count.")
   }
}
func words(data []string) <-chan string {...}

这个示例引入了 done 通道,用于在处理完成时发出信号。在 select 语句中, <-done 操作会阻塞,直到 goroutine 关闭 done 通道。 time.After() 函数返回一个通道,该通道会在指定的持续时间后关闭。如果在 done 通道关闭之前200微秒过去了, time.After() 返回的通道会先关闭,从而使超时情况优先发生。

1.2 sync包的使用

在某些情况下,使用传统方法访问共享值比使用通道更简单和合适。 sync 包提供了几个同步原语,包括互斥锁(mutex)和同步屏障,用于安全地访问共享值。

  • 互斥锁同步 :互斥锁允许对共享资源进行串行访问,通过使 goroutine 阻塞并等待锁释放来实现。以下是一个典型的代码场景, Service 类型必须在启动后才能使用,启动后会更新一个内部的 bool 变量 started 来存储其当前状态:
type Service struct {
   started bool
   stpCh   chan struct{}
   mutex   sync.Mutex
}
func (s *Service) Start() {
   s.stpCh = make(chan struct{})
   go func() {
         s.mutex.Lock()
         s.started = true
         s.mutex.Unlock()
         <-s.stpCh // wait to be closed.
   }()
}
func (s *Service) Stop() {
   s.mutex.Lock()
   defer s.mutex.Unlock()
   if s.started {
         s.started = false
         close(s.stpCh)
   }
}
func main() {
   s := &Service{}
   s.Start()
   time.Sleep(time.Second) // do some work
   s.Stop()
}

上述代码使用 sync.Mutex 类型的 mutex 变量来同步对共享变量 started 的访问。为了有效工作,所有更新 started 变量的竞争区域都必须使用相同的锁,并依次调用 mutex.Lock() mutex.Unlock()

还可以将 sync.Mutex 类型直接嵌入结构体中,这样可以将 Lock() Unlock() 方法提升为结构体本身的方法:

type Service struct {
   ...
   sync.Mutex
}
func (s *Service) Start() {
   s.stpCh = make(chan struct{})
   go func() {
         s.Lock()
         s.started = true
         s.Unlock()
         <-s.stpCh // wait to be closed.
   }()
}
func (s *Service) Stop() {
   s.Lock()
   defer s.Unlock()
   ...
}
  • 读写锁(RWMutex) sync 包还提供了 RWMutex (读写锁),适用于有一个写入者更新共享资源,而可能有多个读取者的情况。写入者使用完全锁更新资源,而读取者使用 RLock() / RUnlock() 方法对来应用只读锁。以下是一个使用 RWMutex 同步访问复合值的示例:
type Service struct {
   started bool
   stpCh   chan struct{}
   mutex   sync.RWMutex
   cache   map[int]string
}
func (s *Service) Start() {
   ...
   go func() {
         s.mutex.Lock()
         s.started = true
         s.cache[1] = "Hello World"
         ...
         s.mutex.Unlock()
         <-s.stpCh // wait to be closed.
   }()
}
...
func (s *Service) Serve(id int) {
   s.mutex.RLock()
   msg := s.cache[id]
   s.mutex.RUnlock()
   if msg != "" {
         fmt.Println(msg)
   } else {
         fmt.Println("Hello, goodbye!")
   }
}

该代码使用 RWMutex 变量来管理对 cache 变量的锁。更新 cache 变量时使用 mutex.Lock() mutex.Unlock() ,读取时使用 mutex.RLock() mutex.RUnlock() 以提供并发安全性。

  • 使用 sync.WaitGroup 创建并发屏障 sync.WaitGroup 类型用于创建同步屏障,允许等待所有运行的 goroutine 完成后再继续执行。使用 WaitGroup 需要三个步骤:
    1. 通过 Add 方法设置组中的参与者数量。
    2. 每个 goroutine 调用 Done 方法来信号完成。
    3. 使用 Wait 方法阻塞,直到所有 goroutine 完成。

以下代码展示了如何使用 WaitGroup 来计算3和5的倍数之和:

const MAX = 1000
func main() {
   values := make(chan int, MAX)
   result := make(chan int, 2)
   var wg sync.WaitGroup
   wg.Add(2)
   go func() { // gen multiple of 3 & 5 values
         for i := 1; i < MAX; i++ {
               if (i%3) == 0 || (i%5) == 0 {
                     values <- i // push downstream
               }
         }
         close(values)
   }()
   work := func() { // work unit, calc partial result
         defer wg.Done()
         r := 0
         for i := range values {
               r += i
         }
         result <- r
   }
   // distribute work to two goroutines
   go work()
   go work()
   wg.Wait()                    // wait for both groutines
   total := <-result + <-result // gather partial results
   fmt.Println("Total:", total)
}

在上述代码中, wg.Add(2) 配置了 WaitGroup 变量 wg ,因为工作分配给了两个 goroutine。 work 函数调用 defer wg.Done() 在每次完成时将 WaitGroup 计数器减一。最后, wg.Wait() 方法调用会阻塞,直到其内部计数器达到零。

  • 检测竞态条件 :调试具有竞态条件的并发代码可能非常耗时和令人沮丧。幸运的是,从Go 1.1版本开始,Go在其命令行工具链中包含了一个竞态检测器。在构建、测试、安装或运行Go源代码时,只需添加 -race 命令标志即可启用竞态检测器。例如,运行带有竞态条件的代码:
$> go run -race sync1.go

编译器的输出将显示导致竞态条件的违规 goroutine 位置。

  • Go中的并行性 :Go运行时调度器会自动在可用的操作系统管理的线程上多路复用和调度 goroutine。这意味着可以并行化的并发程序能够在几乎无需配置的情况下利用底层处理器核心。以下代码通过启动多个 goroutine 来计算3和5的倍数之和:
const MAX = 1000
const workers = 2
func main() {
   values := make(chan int)
   result := make(chan int, workers)
   var wg sync.WaitGroup
   go func() { // gen multiple of 3 & 5 values
         for i := 1; i < MAX; i++ {
               if (i%3) == 0 || (i%5) == 0 {
                     values <- i // push downstream
               }
         }
         close(values)
   }()
   work := func() { // work unit, calc partial result
         defer wg.Done()
         r := 0
         for i := range values {
               r += i
         }
         result <- r
   }
   //launch workers
   wg.Add(workers)
   for i := 0; i < workers; i++ {
         go work()
   }
   wg.Wait() // wait for all groutines
   close(result)
   total := 0
   // gather partial results
   for pr := range result {
         total += pr
   }
   fmt.Println("Total:", total)
}

在多核机器上执行时,每个 goroutine 会自动并行启动。Go运行时调度器默认会创建与CPU核心数相等的操作系统支持的线程用于调度,这个数量由 GOMAXPROCS 运行时值标识。可以通过命令行环境变量或 runtime 包中的 GOMAXPROCS() 函数来显式更改 GOMAXPROCS 值,以微调参与调度 goroutine 的线程数量。

2. Go语言中的数据IO

Go语言将数据输入和输出建模为从源到目标的数据流。 io 包提供了 io.Reader io.Writer 接口,用于实现数据的读取和写入操作。

2.1 io.Reader 接口

io.Reader 接口非常简单,只包含一个 Read([]byte)(int, error) 方法,用于将数据从源读取并传输到提供的字节切片中。以下是一个简单的实现, alphaReader 类型用于过滤非字母字符:

type alphaReader string
func (a alphaReader) Read(p []byte) (int, error) {
   count := 0
   for i := 0; i < len(a); i++ {
         if (a[i] >= 'A' && a[i] <= 'Z') ||
               (a[i] >= 'a' && a[i] <= 'z') {
               p[i] = a[i]
         }
         count++
   }
   return count, io.EOF
}
func main() {
   str := alphaReader("Hello! Where is the sun?")
   io.Copy(os.Stdout, &str)
   fmt.Println()
}

由于 alphaReader 类型实现了 io.Reader 接口,因此可以在任何需要读者的地方使用它。

还可以通过包装现有读者来创建新的读者。以下是更新后的 alphaReader ,它接受一个 io.Reader 作为源:

type alphaReader struct {
   src io.Reader
}
func NewAlphaReader(source io.Reader) *alphaReader {
   return &alphaReader{source}
}
func (a *alphaReader) Read(p []byte) (int, error) {
   if len(p) == 0 {
         return 0, nil
   }
   count, err := a.src.Read(p) // p has now source data
   if err != nil {
         return count, err
   }
   for i := 0; i < len(p); i++ {
         if (p[i] >= 'A' && p[i] <= 'Z') ||
               (p[i] >= 'a' && p[i] <= 'z') {
               continue
         } else {
               p[i] = 0
         }
   }
   return count, io.EOF
}
func main() {
   str := strings.NewReader("Hello! Where is the sun?")
   alpha := NewAlphaReader(str)
   io.Copy(os.Stdout, alpha)
   fmt.Println()
}

这种方法的优点是 alphaReader 可以从任何实现了 io.Reader 接口的源读取数据。

2.2 io.Writer 接口

io.Writer 接口同样简单,只包含一个 Write(p []byte)(n int, err error) 方法,用于将数据从提供的字节流写入目标资源。以下是 channelWriter 类型的实现,它将数据流分解并序列化到Go通道中:

type channelWriter struct {
   Channel chan byte
}
func NewChannelWriter() *channelWriter {
   return &channelWriter{
         Channel: make(chan byte, 1024),
   }
}
func (c *channelWriter) Write(p []byte) (int, error) {
   if len(p) == 0 {
         return 0, nil
   }
   go func() {
         defer close(c.Channel) // when done
         for _, b := range p {
               c.Channel <- b
         }
   }()
   return len(p), nil
}

使用 channelWriter 类型很简单,可以直接调用 Write() 方法,也可以与其他IO原语一起使用。以下是使用 fmt.Fprint 函数将字符串序列化到通道的示例:

func main() {
   cw := NewChannelWriter()
   go func() {
         fmt.Fprint(cw, "Stream me!")
   }()
   for c := range cw.Channel {
         fmt.Printf("%c\n", c)
   }
}

也可以使用 io.Copy 函数将文件内容序列化到通道中:

func main() {
   cw := NewChannelWriter()
   file, err := os.Open("./writer2.go")
   if err != nil {
         fmt.Println("Error reading file:", err)
         os.Exit(1)
   }
   _, err = io.Copy(cw, file)
   if err != nil {
         fmt.Println("Error copying:", err)
         os.Exit(1)
   }
   // consume channel
   for c := range cw.Channel {
         fmt.Printf("%c\n", c)
   }
}

通过这些接口和实现,Go语言提供了一种灵活且可互换的方式来处理数据的输入和输出。

总结

Go语言的并发编程和数据IO提供了强大而灵活的工具,帮助开发者编写高效、安全的程序。在并发编程中,通道、 sync 包的同步原语以及竞态检测器等工具使得处理并发问题更加容易。在数据IO方面, io.Reader io.Writer 接口为数据的读取和写入提供了统一的抽象,使得不同的数据源和目标可以方便地进行交互。掌握这些概念和技术,将有助于开发者更好地利用Go语言的特性。

以下是一个简单的流程图,展示了使用 io.Reader io.Writer 进行数据处理的基本流程:

graph LR
    A[数据源] --> B[io.Reader]
    B --> C[处理数据]
    C --> D[io.Writer]
    D --> E[数据目标]

通过这个流程,我们可以清晰地看到数据从源到目标的流动过程。在实际应用中,可以根据具体需求选择不同的 io.Reader io.Writer 实现,以满足各种数据处理场景。

3. 格式化IO与缓冲IO

在Go语言的数据IO操作中,格式化IO和缓冲IO也是非常重要的部分。

3.1 格式化IO

格式化IO主要使用 fmt 包来实现,它提供了一系列的函数,如 fmt.Printf fmt.Fprintf fmt.Sprintf 等,用于格式化输出数据。

  • fmt.Printf :用于将格式化的字符串输出到标准输出。例如:
package main

import "fmt"

func main() {
    name := "John"
    age := 30
    fmt.Printf("My name is %s and I'm %d years old.\n", name, age)
}
  • fmt.Fprintf :用于将格式化的字符串输出到指定的 io.Writer 。例如:
package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("output.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer file.Close()

    name := "John"
    age := 30
    fmt.Fprintf(file, "My name is %s and I'm %d years old.\n", name, age)
}
  • fmt.Sprintf :用于返回格式化后的字符串,而不进行输出。例如:
package main

import (
    "fmt"
)

func main() {
    name := "John"
    age := 30
    message := fmt.Sprintf("My name is %s and I'm %d years old.", name, age)
    fmt.Println(message)
}
3.2 缓冲IO

缓冲IO可以提高IO操作的效率,特别是在频繁进行小数据量的读写操作时。Go语言提供了 bufio 包来实现缓冲IO。

  • bufio.Reader :用于缓冲读取操作。例如:
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("input.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    reader := bufio.NewReader(file)
    line, err := reader.ReadString('\n')
    if err != nil {
        fmt.Println("Error reading line:", err)
        return
    }
    fmt.Println("Read line:", line)
}
  • bufio.Writer :用于缓冲写入操作。例如:
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("output.txt")
    if err != nil {
        fmt.Println("Error creating file:", err)
        return
    }
    defer file.Close()

    writer := bufio.NewWriter(file)
    message := "Hello, World!\n"
    _, err = writer.WriteString(message)
    if err != nil {
        fmt.Println("Error writing to buffer:", err)
        return
    }
    err = writer.Flush()
    if err != nil {
        fmt.Println("Error flushing buffer:", err)
        return
    }
    fmt.Println("Data written successfully.")
}
4. 内存中的IO

在Go语言中,还可以进行内存中的IO操作,主要使用 bytes strings 包。

4.1 bytes.Buffer

bytes.Buffer 是一个可变大小的字节缓冲区,可以作为 io.Reader io.Writer 使用。例如:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer
    buffer.WriteString("Hello, ")
    buffer.WriteString("World!")
    fmt.Println(buffer.String())

    reader := bytes.NewReader(buffer.Bytes())
    data := make([]byte, buffer.Len())
    n, err := reader.Read(data)
    if err != nil {
        fmt.Println("Error reading from buffer:", err)
        return
    }
    fmt.Printf("Read %d bytes: %s\n", n, string(data))
}
4.2 strings.Reader

strings.Reader 用于从字符串中读取数据,实现了 io.Reader 接口。例如:

package main

import (
    "fmt"
    "strings"
)

func main() {
    str := "Hello, World!"
    reader := strings.NewReader(str)
    data := make([]byte, len(str))
    n, err := reader.Read(data)
    if err != nil {
        fmt.Println("Error reading from string:", err)
        return
    }
    fmt.Printf("Read %d bytes: %s\n", n, string(data))
}
5. 数据编码与解码

在数据传输和存储过程中,常常需要对数据进行编码和解码。Go语言提供了多个包来支持不同的编码格式,如 json xml gob 等。

5.1 JSON编码与解码

json 包用于处理JSON格式的数据。以下是一个简单的JSON编码和解码示例:

package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

func main() {
    // 编码
    p := Person{Name: "John", Age: 30}
    data, err := json.Marshal(p)
    if err != nil {
        fmt.Println("Error encoding JSON:", err)
        return
    }
    fmt.Println("Encoded JSON:", string(data))

    // 解码
    var decoded Person
    err = json.Unmarshal(data, &decoded)
    if err != nil {
        fmt.Println("Error decoding JSON:", err)
        return
    }
    fmt.Printf("Decoded Person: %+v\n", decoded)
}
5.2 XML编码与解码

xml 包用于处理XML格式的数据。以下是一个简单的XML编码和解码示例:

package main

import (
    "encoding/xml"
    "fmt"
)

type Person struct {
    XMLName xml.Name `xml:"person"`
    Name    string   `xml:"name"`
    Age     int      `xml:"age"`
}

func main() {
    // 编码
    p := Person{Name: "John", Age: 30}
    data, err := xml.MarshalIndent(p, "", "  ")
    if err != nil {
        fmt.Println("Error encoding XML:", err)
        return
    }
    fmt.Println("Encoded XML:", string(data))

    // 解码
    var decoded Person
    err = xml.Unmarshal(data, &decoded)
    if err != nil {
        fmt.Println("Error decoding XML:", err)
        return
    }
    fmt.Printf("Decoded Person: %+v\n", decoded)
}

总结

Go语言在数据IO方面提供了丰富的功能和工具,涵盖了从基本的读取和写入操作,到格式化IO、缓冲IO、内存中的IO以及数据编码与解码等多个方面。通过合理运用这些功能,可以高效地处理各种数据输入和输出场景。

以下是一个总结表格,展示了Go语言数据IO相关的主要包和功能:
| 包名 | 主要功能 | 示例函数 |
| — | — | — |
| io | 提供 io.Reader io.Writer 接口,用于基本的读写操作 | io.Copy |
| fmt | 提供格式化IO功能 | fmt.Printf fmt.Fprintf fmt.Sprintf |
| bufio | 提供缓冲IO功能 | bufio.Reader bufio.Writer |
| bytes | 用于内存中的字节缓冲区操作 | bytes.Buffer |
| strings | 用于从字符串中读取数据 | strings.Reader |
| encoding/json | 处理JSON格式的数据编码和解码 | json.Marshal json.Unmarshal |
| encoding/xml | 处理XML格式的数据编码和解码 | xml.Marshal xml.Unmarshal |

同时,下面的流程图展示了一个完整的数据处理流程,包含读取、处理、编码、写入等步骤:

graph LR
    A[数据源] --> B[io.Reader]
    B --> C[数据处理]
    C --> D[数据编码]
    D --> E[io.Writer]
    E --> F[数据目标]

通过对这些概念和技术的掌握,开发者可以更好地利用Go语言进行高效、灵活的数据IO操作,满足不同的业务需求。

本项目采用C++编程语言结合ROS框架构建了完整的双机械臂控制系统,实现了Gazebo仿真环境下的协同运动模拟,并完成了两台实体UR10工业机器人的联动控制。该毕业设计在答辩环节获得98分的优异成绩,所有程序代码均通过系统性调试验证,保证可直接部署运行。 系统架构包含三个核心模块:基于ROS通信架构的双臂协调控制器、Gazebo物理引擎下的动力学仿真环境、以及真实UR10机器人的硬件接口层。在仿真验证阶段,开发了双臂碰撞检测算法和轨迹规划模块,通过ROS控制包实现了末端执行器的同步轨迹跟踪。硬件集成方面,建立了基于TCP/IP协议的实时通信链路,解决了双机数据同步和运动指令分发等关键技术问题。 本资源适用于自动化、机械电子、人工智能等专业方向的课程实践,可作为高年级课程设计、毕业课题的重要参考案例。系统采用模块化设计理念,控制核心硬件接口分离架构便于功能扩展,具备工程实践能力的学习者可在现有框架基础上进行二次开发,例如集成视觉感知模块或优化运动规划算法。 项目文档详细记录了环境配置流程、参数调试方法和实验验证数据,特别说明了双机协同作业时的时序同步解决方案。所有功能模块均提供完整的API接口说明,便于使用者快速理解系统架构并进行定制化修改。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
【微电网】【创新点】基于非支配排序的蜣螂优化算法NSDBO求解微电网多目标优化调度研究(Matlab代码实现)内容概要:本文围绕基于非支配排序的蜣螂优化算法(NSDBO)在微电网多目标优化调度中的应用展开研究,提出了一种改进的智能优化算法以解决微电网系统中经济性、环保性和能源效率等多重目标之间的权衡问题。通过引入非支配排序机制,NSDBO能够有效处理多目标优化中的帕累托前沿搜索,提升解的多样性和收敛性,并结合Matlab代码实现仿真验证,展示了该算法在微电网调度中的优越性能和实际可行性。研究涵盖了微电网典型结构建模、目标函数构建及约束条件处理,实现了对风、光、储能及传统机组的协同优化调度。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事微电网、智能优化算法应用的工程技术人员;熟悉优化算法能源系统调度的高年级本科生亦可参考。; 使用场景及目标:①应用于微电网多目标优化调度问题的研究仿真,如成本最小化、碳排放最低供电可靠性最高之间的平衡;②为新型智能优化算法(如蜣螂优化算法及其改进版本)的设计验证提供实践案例,推动其在能源系统中的推广应用;③服务于学术论文复现、课题研究或毕业设计中的算法对比性能测试。; 阅读建议:建议读者结合文中提供的Matlab代码进行实践操作,重点关注NSDBO算法的核心实现步骤微电网模型的构建逻辑,同时可对比其他多目标算法(如NSGA-II、MOPSO)以深入理解其优势局限,进一步开展算法改进或应用场景拓展。
内容概要:本文详细介绍了使用ENVISARscape软件进行DInSAR(差分干涉合成孔径雷达)技术处理的完整流程,涵盖从数据导入、预处理、干涉图生成、相位滤波相干性分析、相位解缠、轨道精炼重去平,到最终相位转形变及结果可视化在内的全部关键步骤。文中以Sentinel-1数据为例,系统阐述了各环节的操作方法参数设置,特别强调了DEM的获取处理、基线估算、自适应滤波算法选择、解缠算法优化及轨道精炼中GCP点的应用,确保最终获得高精度的地表形变信息。同时提供了常见问题的解决方案实用技巧,增强了流程的可操作性和可靠性。; 适合人群:具备遥感GIS基础知识,熟悉ENVI/SARscape软件操作,从事地质灾害监测、地表形变分析等相关领域的科研人员技术人员;适合研究生及以上学历或具有相关项目经验的专业人员; 使用场景及目标:①掌握DInSAR技术全流程处理方法,用于地表沉降、地震形变、滑坡等地质灾害监测;②提升对InSAR数据处理中关键技术环节(如相位解缠、轨道精炼)的理解实操能力;③实现高精度形变图的生成Google Earth可视化表达; 阅读建议:建议结合实际数据边学边练,重点关注各步骤间的逻辑衔接参数设置依据,遇到DEM下载失败等问题时可参照文中提供的多种替代方案(如手动下载SRTM切片),并对关键结果(如相干性图、解缠图)进行质量检查以确保处理精度。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值