适用于Golang的专家:Go的网络,多线程,数据结构和机器学习

图片 你好居住者!



您是否已经熟悉Go语言的基础知识?那本书适合你。Michalis Tsukalos将演示该语言的功能,给出清晰而简单的解释,给出示例并提出有效的编程模式。在探索Go的细微差别时,您将掌握该语言的数据类型和结构,以及打包,并发,网络编程,编译器设计,优化等。每章末尾的材料和练习将有助于增强您的新知识。独特的材料将是Go中机器学习的一章,它将带您从基本统计技术到回归和聚类。您将学习分类,神经网络和异常检测技术。在应用部分中,您将学习如何将Go与Docker和Kubernetes,Git,WebAssembly,JSON等结合使用。



这本书是关于什么的
1 «Go » Go , godoc , Go-. , . , Go .



2 «Go » Go . unsafe, , Go- C, C- — Go.



, defer, strace(1) dtrace(1). , Go, Go WebAssembly.



3 « Go» , Go: , -, , , , . !



4 « » Go struct, , , , . , switch, strings math/big, Go « — » XML JSON.



5 « Go » , Go . , , -, , . Go container, , Go .



6 « Go» , init(), Go- syscall, text/template html/template. , , go/scanner, go/parser go/token. Go!



7 « » Go: , . , - Go Go- Delve.



8 « UNIX-, » Go. , flag , UNIX, , bytes, io.Reader io.Writer, Viper Cobra Go. : Go, Go Systems Programming!



9 « Go: , » , — , Go.



, , , sync Go.



10 « Go: » . , ! Go, select, Go, , , sync.Mutex sync.RWMutex. context, , (race conditions).



11 «, » , , - , , Go-, Go-.



12 « Go» net/http , - - Go. http.Response, http.Request http.Transport, http.NewServeMux. , Go -! , Go DNS-, Go gRPC.



13 « : » HTTPS- Go UDP TCP net. , RPC, Go TCP- «» .



14 « Go» Go , , , , , TensorFlow, Go Apache Kafka.



. Go, , Go-, Go-, Go C WebAssembly, Go. 5 6 7. Go- , Go- Go.



Go. 8–11 Go, Go, , . Go.

Go WebAssembly, Docker Go, Viper Cobra, JSON YAML, , , go/scanner go/token, git(1) GitHub, atomic, Go gRPC HTTPS.



, Go-, . : -, , , -, .



再说一次Go-channels



一旦使用了select关键字,将有多种使用Go管道的独特方法,它们的作用远不如您在第9章中看到的。在本节中,您将学习使用Go管道的不同方法。



让我提醒您,通道的零值为nil,如果您向关闭的通道发送消息,程序将进入紧急模式。但是,如果您尝试从封闭的通道读取数据,则此通道类型的值将为零。因此,关闭通道后,您将无法再向其中写入数据,但仍可以读取它。



对于要关闭的通道,不必将其设计为仅接收数据。另外,通道零始终处于阻塞状态,也就是说,尝试从通道零读取或写入将阻塞该通道。当您想通过将channel变量设置为nil来禁用select语句的分支时,channels的此属性非常有用。



最后,当试图关闭零通道时,程序将引发恐慌。让我们看一下closeNilChannel.go示例:



package main

func main() {
      var c chan string
      close(c)
}


执行closeNilChannel.go将产生以下结果:



$ go run closeNilChannel.go
panic: close of nil channel
goroutine 1 [running]:
main.main()
       /Users/mtsouk/closeNilChannel.go:5 +0x2a
exit status 2


信号通道



信令信道是仅用于信令的信道。简而言之,当您想通知其他程序某些信息时,可以使用信号通道。信令信道不需要用于传输数据。



信号通道不应与第8章中讨论的UNIX信号处理混淆,它们是完全不同的东西。


本章稍后将讨论使用信令通道的代码示例。



缓冲通道



本小节的主题是缓冲管道。这些通道使Go调度程序可以快速将作业排队以处理更多请求。另外,它们可以用作信号灯以限制应用程序的带宽。



这里介绍的方法是这样的:将所有传入的请求重定向到一个通道,该通道依次处理它们。通道处理完请求后,它将向原始调用方发送一条消息,告知该通道已准备好处理新请求。因此,通道的缓冲区容量限制了该通道可以存储的并发请求数。



我们将使用bufChannel.go程序代码作为示例来研究此方法。让我们将其分为四个部分。



bufChannel.go代码的第一部分如下所示:



package main

import (
       "fmt"
)


bufChannel.go文件的第二部分包含以下Go代码:



func main() {
      numbers := make(chan int, 5)
      counter := 10


此处提供的数字定义允许您在此管道中最多存储五个整数。



bufChannel.go的第三部分包含以下Go代码:



for i := 0; i < counter; i++ {
     select {
     case numbers <- i:
     default:
            fmt.Println("Not enough space for", i)
     }
}


在这段代码中,我们尝试在数字通道中放置十个数字。但是,由于数字只能容纳五个整数,因此我们无法在其中存储所有十个整数。



来自bufChannel.go的其余Go代码如下所示:



   for i := 0; i < counter+5; i++ {
        select {
              case num := <-numbers:
                    fmt.Println(num)
              default:
                    fmt.Println("Nothing more to be done!")
              break
        }
   }
}


在此Go代码中,我们尝试使用for循环和select语句读取数字通道的内容。只要在数字通道中有需要读取的内容,就会执行select语句的第一个分支。当数字通道为空时,将执行默认分支。



执行bufChannel.go将产生以下结果:



$ go run bufChannel.go
Not enough space for 5
Not enough space for 6
Not enough space for 7
Not enough space for 8
Not enough space for 9
0
1
2
3
4
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!
Nothing more to be done!


零通道



在本节中,您将了解通道零。这是一种总是被阻止的特殊通道。我们将以nilChannel.go程序为例来介绍这些频道。让我们将其分为四段代码。



nilChannel.go的第一部分如下所示:



package main

import (
       "fmt"
       "math/rand"
       "time"
)


nilChannel.go的第二部分包含以下Go代码:



func add(c chan int) {
      sum := 0
      t := time.NewTimer(time.Second)

      for {
           select {
           case input := <-c:
                 sum = sum + input
           case <-t.C:
                 c = nil
                 fmt.Println(sum)
           }
      }
}


在这里,以add()函数为例,说明如何使用零通道。<-tC运算符在对time.NewTimer()的调用中指定的时间内阻塞计时器t的C通道。不要将属于函数参数的通道c与属于计时器t的通道tC混淆。时间到后,计时器将一个值发送到tC通道,该通道将启动执行select语句的相应分支-将通道c设置为nil并显示sum变量的值。



第三个nilChannel.go代码段如下所示:



func send(c chan int) {
      for {
           c <- rand.Intn(10)
      }
}


send()函数的目的是生成随机数,并在通道打开时将其发送到通道。



nilChannel.go中的其余Go代码如下所示:



func main() {
      c := make(chan int)
      go add(c)
      go send(c)
      time.Sleep(3 * time.Second)
}


需要使用time.Sleep()函数,以便两个goroutine有足够的时间执行。



运行nilChannel.go将产生以下结果:



$ go run nilChannel.go
13167523
$ go run nilChannel.go
12988362


由于执行add()中的select语句的第一个分支的次数不固定,因此多次运行nilChannel.go将产生不同的结果。



频道频道



通道通道是一种特殊的通道变量,可与其他通道一起使用,而不是通常的变量类型。但是,您仍然需要声明一个通道的数据类型。要定义通道的通道,请连续两次使用chan关键字,如以下语句所示:



c1 := make(chan chan int)


本章介绍的其他类型的渠道比渠道渠道更受欢迎和有用。


我们将使用chSquare.go文件中的示例代码逐步介绍如何使用通道。让我们将其分为四个部分。



chSquare.go的第一部分如下所示:



package main

import (
       "fmt"
       "os"
       "strconv"
       "time"
)

var times int


chSquare.go的第二部分包含以下Go代码:



func f1(cc chan chan int, f chan bool) {
      c := make(chan int)
      cc <- c
      defer close(c)

      sum := 0
      select {
      case x := <-c:
            for i := 0; i <= x; i++ {
                 sum = sum + i
            }
            c <- sum
      case <-f:
            return
      }
}


声明了int类型的常规通道后,我们将其传递给channel通道变量。然后,使用select语句,我们有机会从常规int通道读取数据或使用信号通道f退出函数。



从通道c读取一个值后,我们运行一个for循环,该循环计算从0到刚读取的整数值的所有整数之和。然后,我们将计算出的值发送到int通道c,仅此而已。



chSquare.go的第三部分包含以下Go代码:



func main() {
      arguments := os.Args
      if len(arguments) != 2 {
          fmt.Println("Need just one integer argument!")
          return
      }
      times, err := strconv.Atoi(arguments[1])
      if err != nil {
           fmt.Println(err)
           return
      }

      cc := make(chan chan int)


在此代码段的最后一行,我们声明一个名为cc的通道变量。该变量是该程序的核心,因为一切都取决于它。 cc变量传递给f1(),并在下一个for循环中使用。



chSquare.go Go代码的其余部分如下所示:



   for i := 1; i < times+1; i++ {
        f := make(chan bool)
        go f1(cc, f)
        ch := <-cc
        ch <- i
        for sum := range ch {
             fmt.Print("Sum(", i, ")=", sum)
        }
        fmt.Println()
        time.Sleep(time.Second)
        close(f)
    }
}


完成所有工作后,通道f是goroutine结束的信号通道。 ch:= <-cc指令使您可以从通道变量获取常规通道,并使用ch <-i运算符在其中传递int值。之后,我们使用for循环从管道读取数据。 f1()函数被编程为返回一个值,但是我们也可以读取多个值。请注意,每个i值都由其自己的goroutine提供服务。



信号通道类型可以是任何内容,包括上一代码中使用的布尔值和结构{},它们将在下一部分中用于信号通道。类型为struct {}的信令通道的主要优点是无法将数据发送到这样的通道,这可以防止发生错误。



执行chSquare.go将产生如下结果:



$ go run chSquare.go 4
Sum(1)=1
Sum(2)=3
Sum(3)=6
Sum(4)=10
$ go run chSquare.go 7
Sum(1)=1
Sum(2)=3
Sum(3)=6
Sum(4)=10
Sum(5)=15
Sum(6)=21
Sum(7)=28


选择执行goroutine的顺序



您无需对goroutine的执行顺序做任何假设。但是,有时需要控制此顺序。在本小节中,您将学习如何使用信令通道进行此操作。



您可能会问:“为什么在使用常规函数更容易做到这一点时,先创建goroutine,然后以给定的顺序执行它们?” 答案很简单:goroutine可以并发运行并等待其他goroutine完成,而函数不能执行,因为它们是顺序执行的。


在本小节中,我们将查看一个名为defineOrder.go的Go程序。让我们将其分为五个部分。defineOrder.go的第一部分如下所示:



package main

import (
       "fmt"
       "time"
)

func A(a, b chan struct{}) {
      <-a
      fmt.Println("A()!")
      time.Sleep(time.Second)
      close(b)
}


函数A()被存储在参数a中的通道锁定。在main()中解锁该通道后,A()函数将开始工作。最后,它将关闭通道b,从而取消阻塞另一个功能-在这种情况下为B()。



defineOrder.go的第二部分包含以下Go代码:



func B(a, b chan struct{}) {
      <-a
      fmt.Println("B()!")
      close(b)
}


函数B()的逻辑与函数A()的逻辑相同。该功能在通道a关闭之前一直处于阻塞状态。然后,它完成工作并关闭通道b。请注意,通道a和b指的是功能参数名称。



defineOrder.go的第三段代码如下所示:



func C(a chan struct{}) {
      <-a
      fmt.Println("C()!")
}


C()函数被阻止,并在启动前等待通道a关闭。



defineOrder.go的第四部分包含以下代码:



func main() {
      x := make(chan struct{})
      y := make(chan struct{})
      z := make(chan struct{})


这三个通道将成为三个功能的参数。



defineOrder.go的最后一个代码片段包含以下Go代码:



     go C(z)
     go A(x, y)
     go C(z)
     go B(y, z)
     go C(z)

     close(x)
     time.Sleep(3 * time.Second)
}


在这里,程序执行所有必要的功能,然后关闭x通道并休眠三秒钟。



即使将多次调用C()函数,执行defineOrder.go也会产生所需的结果:



$ go run defineOrder.go
A()!
B()!
C()!
C()!
C()!


作为goroutine多次调用C()不会导致问题,因为C()不会关闭任何通道。但是,如果多次调用A()或B(),则很可能会显示错误消息,例如:



$ go run defineOrder.go
A()!
A()!
B()!
C()!
C()!
C()!
panic: close of closed channel
goroutine 7 [running]:
main.A(0xc420072060, 0xc4200720c0)
       /Users/mtsouk/Desktop/defineOrder.go:12 +0x9d
created by main.main
       /Users/mtsouk/Desktop/defineOrder.go:33 +0xfa
exit status 2


如您所见,这里的A()函数被调用了两次。但是,当A()关闭通道时,其中一个goroutine程序检测到该通道已关闭,并在尝试再次关闭该通道时产生了紧急情况。如果我们尝试多次调用B()函数,则会遇到类似的紧急情况。



如何不使用goroutines



在本节中,您将学习使用goroutines对自然数进行排序的天真的方法。我们将要查看的程序称为sillySort.go。让我们将其分为两部分。sillySort.go的第一部分如下所示:



package main

import (
       "fmt"
       "os"
       "strconv"
       "sync"
       "time"
)

func main() {
      arguments := os.Args

      if len(arguments) == 1 {
          fmt.Println(os.Args[0], "n1, n2, [n]")
          return
      }

      var wg sync.WaitGroup
      for _, arg := range arguments[1:] {
           n, err := strconv.Atoi(arg)
           if err != nil || n < 0 {
                fmt.Print(". ")
                continue
           }


sillySort.go的第二部分包含以下Go代码:



           wg.Add(1)
           go func(n int) {
                defer wg.Done()
                time.Sleep(time.Duration(n) * time.Second)
                fmt.Print(n, " ")
           }(n)
      }

      wg.Wait()
      fmt.Println()
}


排序是通过调用time.Sleep()函数完成的-自然数越大,执行fmt.Print()运算符之前需要的时间越长!



执行sillySort.go将产生如下结果:



$ go run sillySort.go a -1 1 2 3 5 0 100 20 60
. . 0 1 2 3 5 20 60 100
$ go run sillySort.go a -1 1 2 3 5 0 100 -1 a 20 hello 60
. . . . . 0 1 2 3 5 20 60 100
$ go run sillySort.go 0 0 10 2 30 3 4 30
0 0 2 3 4 10 30 30




关于作者



Mihalis Tsoukalos是UNIX管理员,程序员,数据库管理员和数学家。喜欢写技术书籍和文章,学习新东西。除本书外,Michalis还为许多杂志撰写了Go Systems Programming以及250余篇技术文章,包括Sys Admin,MacTech,Linux User and Developer,Usenix; login:,Linux Format和Linux Journal。Michalis的研究兴趣是数据库,可视化,统计和机器学习。



关于科学编辑



Mat Ryer从六岁起就开始编写计算机程序:首先是ZX Spectrum的BASIC,然后是他的父亲,是Commodore Amiga的AmigaBASIC和AMOS。他花了很多时间从Amiga格式日志中手动复制代码,更改变量或GOTO语句引用的值以查看结果。出于对编程的探索和痴迷,同样的精神使18岁的Matt在英国曼斯菲尔德的一家本地组织工作,在那里他开始建立网站和其他在线服务。



Mat不仅在伦敦,而且在世界各地使用不同领域的不同技术工作了数年之后,Mat将注意力转向了一种名为Go的新系统编程语言,该语言最早在Google使用。由于Go正在解决非常棘手的技术问题,因此当Go仍处于测试版时,Mat开始使用该语言来解决问题,并从那时起一直在使用它进行编程。 Mat从事过多个开源项目,创建了多个Go软件包,包括Testify,Moq,Silk和Is,以及MacOS开发人员工具包BitBar。



自2018年以来,Mat一直是Machine Box的联合创始人,但他仍然参加会议,在博客上写有关Go的文章,并且是Go社区的活跃成员。



»有关这本书的更多详细信息,请参见出版商的网站

»目录

»摘录



居住者的优惠券可享受25%的折扣-Golang



在为该书的纸质版本付款后,会向该电子邮件发送一本电子书。



All Articles