简介 程序搭建是动手做一个程序基础。这里记录一下基本的脚手架相关的东西。
suture 当使用 https://github.com/thejerf/suture
库时,你可以按照以下方式修改之前的代码:
首先,确保你已经安装了 suture
库。你可以通过以下命令安装:
1 go get -u github.com/thejerf/suture
然后,使用 suture
的 Supervisor
类型替换我们之前手动实现的 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 sutureimport ( "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: i.stop <- true return } } } func (i *IncrementorJob) Complete() bool { 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() }
在这个例子中,我们使用了 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 mainimport ( "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
,它代表整个命令行应用程序。然后,我们创建了两个子命令 helloCmd
和 greetCmd
,分别用于打印 “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 mainimport "fmt" type Product struct { Name string Price float64 Quantity int } type Option func (*Product) func WithName (name string ) Option { return func (p *Product) { p.Name = name } } func WithPrice (price float64 ) Option { return func (p *Product) { p.Price = price } } func WithQuantity (quantity int ) Option { return func (p *Product) { p.Quantity = quantity } } 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 singletonimport "sync" type singleton struct { } var instance *singletonvar once sync.Oncefunc 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 mainimport ( "fmt" "singleton" ) func main () { instance1 := singleton.GetInstance() instance2 := singleton.GetInstance() fmt.Println(instance1 == instance2) }
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 mainimport ( "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 mainimport ( "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" }() 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 mainimport ( "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 ) fmt.Println("Task started at" , time.Now()) time.Sleep(3 * time.Second) fmt.Println("Task finished at" , time.Now()) } for { select { case <-ticker.C: task() } } }
代码解释:
原子变量 running
:
使用 int32
类型的变量 running
来标识任务是否在运行。通过原子操作来保证并发安全。
atomic.CompareAndSwapInt32
:
在任务开始前使用 atomic.CompareAndSwapInt32(&running, 0, 1)
来判断当前是否有任务在运行。如果 running
的值是 0,则将其设置为 1 并继续执行任务;如果不是 0,说明有任务正在运行,直接返回。
任务完成后重置 running
:
在任务执行完成后,使用 defer atomic.StoreInt32(&running, 0)
将 running
重置为 0,表示任务已完成。
time.NewTicker
:
创建一个 2 秒间隔的 Ticker
,每当 ticker.C
通道接收到信号时,执行 task
函数。
这种方式确保了在处理当前 tick 时,如果新的 tick 到来,能够有效丢弃新的 tick,避免由于任务执行时间长而造成的拥塞和挤压。
引用