go语言基本介绍及并发机制

go的简介

Go 语言又称 Golang,由 Google 公司于 2009 年发布,近几年伴随着云计算、微服务、分布式的发展而迅速崛起,跻身主流编程语言之列,和 Java 类似,它是一门静态的、强类型的、编译型编程语言,为并发而生,所以天生适用于并发编程(网络编程)。

目前 Go 语言支持 Windows、Linux 等多个平台,也可以直接在 Android 和 iOS 等移动端执行,从业务角度来看,Go 语言在云计算、微服务、大数据、区块链、物联网、人工智能等领域都有广泛的应用。

与其他语言的对比

C/C++

Go 设计的初衷是替代 C,所以二者有很多相似之处,但 Go 做的更多:

  • 提供了自动管理线程和垃圾回收的运行时,在 C/C++ 中,需要自行管理线程和内存
  • 更快的编译速度

因此,相对 C/C++,Go 开发效率更高。

适用场景不同:

  • C/C++ 可用于高性能嵌入式系统、大型云应用以及桌面程序开发
  • Go 适用于系统和云平台开发

Go 不适用于高性能嵌入式系统,因为嵌入式系统资源有限,而 Go 运行时调度线程和垃圾回收需要额外的开销。至今没有提供 GUI SDK,所以也不适用于桌面程序开发。

Java

Java 程序编译之后需要安装额外的 Java runtime 运行,Java 程序的可移植性依赖 Java runtime,Go 不需要,Go 运行时已经包含在这个编译的二进制文件中了,这体现在部署上的区别就是需要在服务器安装 Java runtime,而 Go 只需要部署单文件即可。

PHP

PHP 是动态语言,而 Go 是静态语言,会做类型检查,可靠性更高。

开发 Web 应用时,PHP 通常躲在 Nginx/Apache 后面作为后台进程,Go 则提供了内置的 Web 服务器,完全可以直接在生产环境使用。

PHP 之所以要借助额外的 Web 服务器是因为对并发请求的处理,PHP 本身就没有多线程多进程机制,一次请求从头到位都是一个独立的进程,为了让基于 PHP 的 Web 应用支持并发请求,必须借助外部 Web 服务器。

而 Go 内置的 Web 服务器充分利用了 goroutine,对并发连接有很好的支持。此外,由于协程的本质是在在同一个进程中调度不同线程,所以还支持共享资源。

PHP 作为动态语言,性能不如 Go,如果要提升性能,必须通过 C 语言编写扩展,复杂度和学习成本太高。

语言特性

语法简单,并发模型(Goroutine ),内存回收(GC),内存分配等

基本数据类型及声明方式

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128
1
2
3
4
//标准声明
var name string
//简短声明
name := "张三"

结构体(struct)

结构体成员是由一系列的成员变量构成.字段有以下特性:

  • 字段拥有自己的类型和值。
  • 字段名必须唯一。
  • 字段的类型也可以是结构体,甚至是字段所在结构体的类型。

Go 语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。

Go 语言的结构体与“类”都是复合结构体,但 Go 语言中结构体的内嵌配合接口比面向对象具有更高的扩展性和灵活性。

Go 语言不仅认为结构体能拥有方法,且每种自定义类型也可以拥有自己的方法。

使用Go语言的结构体内嵌实现对象特性组合

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
package main

import "fmt"

// Flying 可飞行的
type Flying struct{}

func (f *Flying) Fly() {
fmt.Println("can fly")
}

// Walkable 可行走的
type Walkable struct{}

func (f *Walkable) Walk() {
fmt.Println("can calk")
}

// Human 人类
type Human struct {
sex string // 人的性别
age int // 人的年龄
Walkable // 人类能行走
}

// Bird 鸟类
type Bird struct {
Walkable // 鸟类能行走
Flying // 鸟类能飞行
}

func main() {

// 实例化鸟类
b := new(Bird)
fmt.Println("Bird: ")
b.Fly()
b.Walk()

// 实例化人类
h := new(Human)
fmt.Println("Human: ")
h.Walk()

}

Goroutine

Go语言最大的特色就是从语言层面支持并发(Goroutine),Goroutine是Go中最基本的执行单元。事实上每一个Go程序至少有一个Goroutine:主Goroutine。当程序启动时,它会自动创建。

为了更好理解Goroutine,现讲一下线程和协程的概念

线程(Thread):有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程的切换一般也由操作系统调度。

协程(coroutine):又称微线程与子例程(或者称为函数)一样,协程(coroutine)也是一种程序组件。相对子例程而言,协程更为一般和灵活,但在实践中使用没有子例程那样广泛。

和线程类似,共享堆,不共享栈,协程的切换一般由程序员在代码中显式控制。它避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂。

Goroutine和其他语言的协程(coroutine)在使用方式上类似,但从字面意义上来看不同(一个是Goroutine,一个是coroutine),再就是协程是一种协作任务控制机制,在最简单的意义上,协程不是并发的,而Goroutine支持并发的。因此Goroutine可以理解为一种Go语言的协程。同时它可以运行在一个或多个线程上。

GO并发的实现原理

一、Go并发模型

Go实现了两种并发形式。第一种是大家普遍认知的:多线程共享内存。其实就是Java或者C++等语言中的多线程开发。另外一种是Go语言特有的,也是Go语言推荐的:CSP(communicating sequential processes)并发模型。

CSP并发模型是在1970年左右提出的概念,属于比较新的概念,不同于传统的多线程通过共享内存来通信,CSP讲究的是“以通信的方式来共享内存”。

普通的线程并发模型,就是像Java、C++、或者Python,他们线程间通信都是通过共享内存的方式来进行的。非常典型的方式就是,在访问共享数据(例如数组、Map、或者某个结构体或对象)的时候,通过锁来访问。Go中也实现了传统的线程并发模型。

Go的CSP并发模型,是通过goroutinechannel来实现的。

  • goroutine 是Go语言中并发的执行单位。有点抽象,其实就是和传统概念上的”线程“类似,可以理解为”线程“。
  • channel是Go语言中各个并发结构体(goroutine)之前的通信机制。 通俗的讲,就是各个goroutine之间通信的”管道“,有点类似于Linux中的管道。

生成一个goroutine的方式非常的简单:Go一下,就生成了。

1
go f();

通信机制channel也很方便,传数据用channel <- data,取数据用<-channel

在通信过程中,传数据channel <- data和取数据<-channel必然会成对出现,因为这边传,那边取,两个goroutine之间才会实现通信。

而且正常情况下不管传还是取,必阻塞,直到另外的goroutine传或者取为止。

不过也可以非阻塞接收数据

使用非阻塞方式从通道接收数据时,语句不会发生阻塞,格式如下:

1
data, ok := <-ch
  • data:表示接收到的数据。未接收到数据时,data 为通道类型的零值。
  • ok:表示是否接收到数据。

非阻塞的通道接收方法可能造成高的 CPU 占用,因此使用非常少

注意 main()本身也是运行了一个goroutine。

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"
"math/rand"
"time"
)

//生产者
func producer(header string, channel chan<- string) {
for {
//随机一个100以内的数,放入管道中
channel <- fmt.Sprintf("%s:%d", header, rand.Intn(100))
time.Sleep(time.Second)
}
}
//消费者
func consumer(channel <-chan string) {
for {
//将管道里的数据取出放入msg变量中
msg := <-channel
fmt.Println(msg)
}
}

func main() {
//声明一个通道
channel := make(chan string)
go producer("dog", channel)
go producer("cat", channel)
consumer(channel)
}

学习路线图

附 Go 语言学习路线图如下:

Go 语言完整知识图谱
Go 语言完整知识图谱