Abel'Blog

我干了什么?究竟拿了时间换了什么?

0%

go-程序搭建

简介

程序搭建是动手做一个程序基础。这里记录一下基本的脚手架相关的东西。

suture

当使用 https://github.com/thejerf/suture 库时,你可以按照以下方式修改之前的代码:

首先,确保你已经安装了 suture 库。你可以通过以下命令安装:

1
go get -u github.com/thejerf/suture

然后,使用 sutureSupervisor 类型替换我们之前手动实现的 Supervisor 结构。下面是一个示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
package suture

import (
"fmt"
"testing"
)

const (
JobLimit = 2
)

type IncrementorJob struct {
current int
next chan int
stop chan bool
}

func (i *IncrementorJob) Stop() {
fmt.Println("Stopping the service")
i.stop <- true
}

func (i *IncrementorJob) Serve() {
for {
select {
case i.next <- i.current + 1:
i.current++
if i.current >= JobLimit {
return
}
case <-i.stop:
// We sync here just to guarantee the output of "Stopping the service",
// so this passes the test reliably.
// Most services would simply "return" here.
i.stop <- true
return
}
}
}

func (i *IncrementorJob) Complete() bool {
// fmt.Println("IncrementorJob exited as Complete()")
return i.current >= JobLimit
}

func TestCompleteJob(t *testing.T) {
supervisor := NewSimple("Supervisor")
service := &IncrementorJob{0, make(chan int), make(chan bool)}
supervisor.Add(service)

supervisor.ServeBackground()

fmt.Println("Got:", <-service.next)
fmt.Println("Got:", <-service.next)

<-service.stop

fmt.Println("IncrementorJob exited as Complete()")

supervisor.Stop()

// Output:
// Got: 1
// Got: 2
// Stopping the service
}

在这个例子中,我们使用了 suture 库的 NewSimple 函数创建了一个简单的 Supervisor,并使用 supervisor.Add(worker) 将我们的 Worker 实例注册到 Supervisor 中。然后,通过 supervisor.ServeBackground() 启动了 Supervisor

主协程等待一段时间后,通过调用 worker.Stop() 停止了工作协程,然后等待 supervisor.Stop() 确保 Supervisor 也停止。最后,打印一条消息表示程序停止。

cobra

github.com/spf13/cobra 是一个用于构建强大的命令行应用程序的Go库。以下是一个简单的例子,演示如何使用 cobra 创建一个命令行工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"fmt"
"github.com/spf13/cobra"
"os"
)

func main() {
var rootCmd = &cobra.Command{Use: "app", Short: "A simple CLI app"}

var helloCmd = &cobra.Command{
Use: "hello",
Short: "Prints 'Hello, World!'",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello, World!")
},
}

var greetCmd = &cobra.Command{
Use: "greet",
Short: "Greet someone",
Run: func(cmd *cobra.Command, args []string) {
if len(args) == 0 {
fmt.Println("Please provide a name to greet.")
} else {
fmt.Printf("Hello, %s!\n", args[0])
}
},
}

rootCmd.AddCommand(helloCmd)
rootCmd.AddCommand(greetCmd)

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

在这个例子中,我们首先创建了一个 rootCmd,它代表整个命令行应用程序。然后,我们创建了两个子命令 helloCmdgreetCmd,分别用于打印 “Hello, World!” 和根据提供的名字打印个性化的问候。

通过 rootCmd.AddCommand 将子命令添加到根命令中。每个子命令都有一个 Run 函数,用于定义该命令执行时的行为。

最后,通过调用 rootCmd.Execute() 来解析命令行参数并执行相应的命令。如果执行过程中出现错误,会打印错误信息并退出应用。

要使用此示例,你可以将其保存为一个Go文件,然后使用以下命令构建和运行:

1
2
3
go build -o myapp
./myapp hello
./myapp greet John

以上命令演示了如何构建一个简单的命令行应用程序,你可以根据实际需求进行扩展和修改。

option模式

Option 模式通常用于在构建复杂对象时提供一种灵活的方式,允许根据需求动态地配置对象的属性。下面是一个简单的示例,演示了如何在 Golang 中实现 Option 模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

import "fmt"

// Product 是一个示例的产品结构体
type Product struct {
Name string
Price float64
Quantity int
}

// Option 是一个函数类型,用于配置 Product
type Option func(*Product)

// WithName 是一个 Option,用于设置产品的名称
func WithName(name string) Option {
return func(p *Product) {
p.Name = name
}
}

// WithPrice 是一个 Option,用于设置产品的价格
func WithPrice(price float64) Option {
return func(p *Product) {
p.Price = price
}
}

// WithQuantity 是一个 Option,用于设置产品的数量
func WithQuantity(quantity int) Option {
return func(p *Product) {
p.Quantity = quantity
}
}

// NewProduct 是一个构造函数,用于创建 Product 对象并应用选项
func NewProduct(opts ...Option) *Product {
// 默认值
product := &Product{
Name: "Default Product",
Price: 0.0,
Quantity: 0,
}

// 应用选项
for _, opt := range opts {
opt(product)
}

return product
}

func main() {
// 创建一个具有不同选项的产品
product1 := NewProduct(
WithName("Product A"),
WithPrice(10.99),
)

// 创建另一个产品,使用默认值
product2 := NewProduct()

fmt.Println("Product 1:", product1)
fmt.Println("Product 2:", product2)
}

在这个示例中,我们定义了一个 Product 结构体和几个用于配置 Product 对象的选项函数。然后,通过 NewProduct 构造函数创建 Product 对象,并根据需要应用选项。这种方式允许我们根据需求动态地配置对象,而无需为每种配置都定义不同的构造函数。

单例模式

在Go中实现单例模式比较简单,因为Go的包机制和init()函数可以确保全局唯一性。以下是一个基本的单例模式示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package singleton

import "sync"

// singleton 是一个单例对象
type singleton struct {
// 在此处添加单例对象的属性
}

var instance *singleton
var once sync.Once

// GetInstance 返回单例对象的实例
func GetInstance() *singleton {
once.Do(func() {
instance = &singleton{}
})
return instance
}

在这个示例中:

  • singleton 结构体表示单例对象,您可以在其中添加所需的属性和方法。
  • instance 变量保存单例对象的实例。
  • once 变量是 Go 标准库 sync 包中的 Once 类型,它确保 GetInstance 函数只执行一次。
  • GetInstance 函数返回单例对象的实例。通过调用 once.Do 方法,可以确保 GetInstance 函数仅在第一次调用时执行初始化代码,后续调用将直接返回之前创建的实例。

使用时,您只需调用 GetInstance 函数即可获得单例对象的实例,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"singleton"
)

func main() {
instance1 := singleton.GetInstance()
instance2 := singleton.GetInstance()

fmt.Println(instance1 == instance2) // 应该输出 true,因为它们是同一个实例
}

select

当涉及到并发编程时,Go语言中的chan和select是非常有用的工具。chan用于在Go协程之间进行通信,而select用于在多个通信操作中选择一个执行。以下是一个使用chan和select搭建的简单程序示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package main

import (
"fmt"
"time"
)

func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d started job %d\n", id, job)
time.Sleep(time.Second) // 模拟任务执行
fmt.Printf("Worker %d finished job %d\n", id, job)
results <- job * 2 // 将处理结果发送到结果通道
}
}

func main() {
numJobs := 5
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)

// 启动三个工作协程
for w := 1; w <= 3; w++ {
go worker(w, jobs, results)
}

// 发送任务到任务通道
for j := 1; j <= numJobs; j++ {
jobs <- j
}

close(jobs) // 关闭任务通道

// 从结果通道接收并打印处理结果
for r := 1; r <= numJobs; r++ {
result := <-results
fmt.Println("Received result:", result)
}
}

在这个示例中,有三个工作协程在worker函数中运行,每个协程从jobs通道中接收任务,并将处理结果发送到results通道。主函数中会向jobs通道发送5个任务,然后关闭jobs通道。接着,主函数从results通道接收处理结果,并打印出来。

select语句通常与chan一起使用,用于在多个通信操作中选择一个执行。以下是一个简单的使用select的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package main

import (
"fmt"
"time"
)

func main() {
ch1 := make(chan string)
ch2 := make(chan string)

go func() {
time.Sleep(2 * time.Second)
ch1 <- "one"
}()

go func() {
time.Sleep(1 * time.Second)
ch2 <- "two"
}()

// 使用select等待ch1或ch2可用
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received from ch1:", msg1)
case msg2 := <-ch2:
fmt.Println("Received from ch2:", msg2)
}
}
}

在这个示例中,有两个协程分别向ch1和ch2通道发送消息,主函数使用select语句等待这两个通道中的一个可用,然后接收并打印收到的消息。

在 Go 中使用 time.Ticker 时,如果上一轮的 tick 还在执行,而新的 tick 已经到来,会造成任务的挤压和拥塞。为了解决这个问题,可以在每次处理 tick 时使用一个非阻塞的通道来丢弃新的 tick,直到当前的任务处理完成。以下是一个示例代码,展示了如何实现这个机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main

import (
"fmt"
"time"
"sync/atomic"
)

func main() {
var running int32 // 使用原子变量来检查任务是否在运行
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()

task := func() {
if !atomic.CompareAndSwapInt32(&running, 0, 1) {
// 如果任务正在运行,直接返回
return
}
defer atomic.StoreInt32(&running, 0) // 任务完成后重置running标识

// 模拟任务处理
fmt.Println("Task started at", time.Now())
time.Sleep(3 * time.Second) // 假设任务需要3秒钟
fmt.Println("Task finished at", time.Now())
}

for {
select {
case <-ticker.C:
task()
}
}
}

代码解释:

  1. 原子变量 running

    • 使用 int32 类型的变量 running 来标识任务是否在运行。通过原子操作来保证并发安全。
  2. atomic.CompareAndSwapInt32

    • 在任务开始前使用 atomic.CompareAndSwapInt32(&running, 0, 1) 来判断当前是否有任务在运行。如果 running 的值是 0,则将其设置为 1 并继续执行任务;如果不是 0,说明有任务正在运行,直接返回。
  3. 任务完成后重置 running

    • 在任务执行完成后,使用 defer atomic.StoreInt32(&running, 0)running 重置为 0,表示任务已完成。
  4. time.NewTicker

    • 创建一个 2 秒间隔的 Ticker,每当 ticker.C 通道接收到信号时,执行 task 函数。

这种方式确保了在处理当前 tick 时,如果新的 tick 到来,能够有效丢弃新的 tick,避免由于任务执行时间长而造成的拥塞和挤压。

引用