Golang教程第1篇:基本概念

目录

1.go语言特性

  • 垃圾回收,内存自动回收,开发人员不需要去释放内存,系统会自动释放内存
  • 天然并发,语言层面就支持并发,goroute,轻量级线程
  • 基于CSP(Communicating Sequential Process)模型实现

2.并发简单演示

在src下创建两个文件
cat testa.go

package main

import (
	"fmt"
)

// TestGoroute is a function.
func TestGoroute(a int, b int) {
	sum := a + b
	fmt.Println(sum)
}

// TestPrint is a function
func TestPrint(a int) {
	fmt.Println(a)

}

testa.go在包package中自定义了两个函数TestGoroute和TestPrint,注意这里的自定义函数上面最好有提示,否则在vscode上会提示编码不规范,同时函数名最好使用大写开头,不要有下划线

cat test.go

package main


import (
	"fmt"
    "time"
)


func add(a int, b int) int {
	var sum int
	sum = a + b
	return sum
}

func main() {
	var c int
	c = add(100, 200)
	go TestGoroute(300, 300)
	fmt.Println("add(100.200)=", c)

	for i := 0; i < 100; i++ {
		go TestPrint(i)
	}
    time.Sleep(time.second)
    
}

test.go里面的for循环调用了testa.go中自定义的函数TestPrint,但这里相当于是起了100个线程实现并发,输出的结果是乱序的,这是因为启用了100个goroute,每个goroute是相互独立的,在调用的时候是随机的

go它天然支持多线程,go进程可以直接跑满cpu,nginx可以支持多线程,这也是基于应对高并发的一个设计,而redis是单进程的,所以一般会在一台机器上其多个redis实例,go在语言层面上实现多线程,因为go的底层就会在机器上七多个线程,开发人员可以通过goroute来实现多线程,不需要像其他语言来建立线程池

3.channel

每个goroute之间是通过channel进行通信

cat pipe.go

package main

import (
	"fmt"
)

//TestPipe is a function
func TestPipe() {
	pipe := make(chan int, 3)
	pipe <- 1
	pipe <- 2
	pipe <- 3

	var t1 int
	t1 = <- pipe
	pipe <- 4
	fmt.Println(t1)
	fmt.Println(len(pipe))
}

pipe := make(chan int,3)这一句表示赋值个变量pipe,创建了一个channel,可以往该channel放3个整型数据
其输入输出依照先进先出的规则,代码中往pipe这个管道中依次存入了1 2 3这三个数据,然后从管道中取出一个数据赋值给变量t1,注意去除的数据为1,这个时候pipe中就空闲了一个空间,后面有放入了4,但其空间位置是在3的后面了,也就是说如果后面接着从管道中取数据,去除的是2,接着是3,最后才是4

理论上管道中输入的数据超过容量限制,默认会报死锁错误退出线程,这样可以即时发现错误
下面用一个例子来说明
修改pipe.go
cat pipe.go

package main

import (
	"fmt"
)

//TestPipe is a function
func TestPipe() {
	pipe := make(chan int, 3)
	pipe <- 1
	pipe <- 2
	pipe <- 3

	/* 	var t1 int
	   	t1 = <-pipe */
	pipe <- 4
	// fmt.Println(t1)
	fmt.Println(len(pipe))
}

然后在别的程序文件中调用函数TestPipe
cat test.go

package main

func add(a int, b int) int {
	var sum int
	sum = a + b
	return sum
}

func main() {
	/* 	var c int
	   	c = add(100, 200)
	   	go TestGoroute(300, 300)
	   	fmt.Println("add(100.200)=", c)

	   	for i := 0; i < 100; i++ {
	   		go TestPrint(i)
	   	}

	   	time.Sleep(time.Second) */
	/* 	fmt.Println("start goroute")
	   	go TestPipe()
	   	fmt.Println("end start goroute")
	   	time.Sleep(10 * time.Second) */
	TestPipe()
}

执行程序报错

goroutine 1 [chan send]:
main.TestPipe()
d:/Go_work/src/pipe.go:16 +0xae
main.main()
d:/Go_work/src/test.go:24 +0x27
exit status 2

这样导致main函数退出

下面放到goroute里面去执行
cat test.go

package main

import (
	"fmt"
	"time"
)

func add(a int, b int) int {
	var sum int
	sum = a + b
	return sum
}

func main() {
	/* 	var c int
	   	c = add(100, 200)
	   	go TestGoroute(300, 300)
	   	fmt.Println("add(100.200)=", c)

	   	for i := 0; i < 100; i++ {
	   		go TestPrint(i)
	   	}

	   	time.Sleep(time.Second) */
	fmt.Println("start goroute")
	go TestPipe()
	fmt.Println("end start goroute")
	time.Sleep(10 * time.Second)
	// TestPipe()
}

执行后输出

start goroute
end start goroute

说明只是退出了goroute线程,但是main函数还是正常执行完成

goroute之间通信是通过channel,但是也可以通过全局变量来实现
cat test.go

package main

import (
	"fmt"
	"time"
)

func add(a int, b int) int {
	var sum int
	sum = a + b
	return sum
}

func main() {
	/* 	var c int
	   	c = add(100, 200)
	   	go TestGoroute(300, 300)
	   	fmt.Println("add(100.200)=", c)

	   	for i := 0; i < 100; i++ {
	   		go TestPrint(i)
	   	}

	   	time.Sleep(time.Second) */
	fmt.Println("start goroute")
	go TestPipe()
	fmt.Println("end start goroute")
	time.Sleep(10 * time.Second)
	// TestPipe()
}

上面调用了函数TestPipe,下面是定义Testpipe的文件
cat pipe.go

package main

import (
	"fmt"
)

//TestPipe is a function
func TestPipe() {
	pipe := make(chan int, 3)
	pipe <- 1
	pipe <- 2
	pipe <- 3
	sum = <-pipe

	/* 	var t1 int
	   	t1 = <-pipe */
	pipe <- 4
	// fmt.Println(t1)
	fmt.Println("sum", sum)
	fmt.Println(len(pipe))
}

上图中引入了一个全局变量sum,下面是定义该变量的文件
cat testa.go

package main

import "fmt"

var sum int

// TestGoroute is a function.
func TestGoroute(a int, b int) {
	sum = a + b
	fmt.Println(sum)
}

// TestPrint is a function
func TestPrint(a int) {
	fmt.Println(a)

}

该文件中定义了全局变量sum,pipe.go调用sum,最后test.go调用pipe.go,生产中不建议使用全局变量,文件数多了有时候出现一个全局变量就会很困惑-这个变量没定义,这会给维护代码的人员造成困扰,不清楚该变量来自哪个文件

在Go语言当中,任何一个变量都需要先定义再赋值使用,如:
var string num
num = "string"
还有一种等价的写法
num := "string"

下面的这种方式更简洁,这是因为下一种方式会根据调用的函数中参数值类型来自动设定num变量的类型

再来看一个goroute与channel的例子
cat pipe.go

package main

import (
	"fmt"
	"time"
)

func add(a int, b int, c chan int) {
	var sum int
	sum = a + b
	c <- sum

}

func main() {
	var pipe chan int
	pipe = make(chan int, 1)
	go add(3, 5, pipe)
	sum := <-pipe
	fmt.Println("sum", sum)
	/* 	var c int
	   	c = add(100, 200)
	   	go TestGoroute(300, 300)
	   	fmt.Println("add(100.200)=", c)

	   	for i := 0; i < 100; i++ {
	   		go TestPrint(i)
	   	}

	   	time.Sleep(time.Second) */
	fmt.Println("start goroute")
	go TestPipe()
	fmt.Println("end start goroute")
	time.Sleep(10 * time.Second)
	// TestPipe()
}

4.多返回值

cat calc.go

package main

func calc(a int, b int) (int, int) {
	c := a + b
	d := (a + b) / 2

	return c, d
} 

先定义一个函数calc,接着在主函数main中调用
cat test.go

package main

import (
	"fmt"
)

func add(a int, b int, c chan int) {
	var sum int
	sum = a + b
	c <- sum

}

func main() {
	/* 	var pipe chan int
	   	pipe = make(chan int, 1)
	   	go add(3, 5, pipe)
	   	sum := <-pipe
	   	fmt.Println("sum", sum) */
	/* 	var c int
	   	c = add(100, 200)
	   	go TestGoroute(300, 300)
	   	fmt.Println("add(100.200)=", c)

	   	for i := 0; i < 100; i++ {
	   		go TestPrint(i)
	   	}

	   	time.Sleep(time.Second) */
	/* 	fmt.Println("start goroute")
	   	go TestPipe()
	   	fmt.Println("end start goroute")
	   	time.Sleep(10 * time.Second) */
	// TestPipe()

	sum, avg := calc(100, 200)
	fmt.Println("sum=", sum, "avg", avg)
}

如果返回值有的不需要可以如下处理,比如avg不打算输出

sum, avg := calc(100, 200)中的avg以下划线替换

5.包的概念

  • 将相同功能的代码放到一个目录,称为包
  • 包可以被其他包调用
  • main包是是用来生成可执行文件,每一个程序只有一个包
  • 包的主要用途是提高代码的复用性
  • 每一个程序文件一定是属于某个包,否则是无法使用的

一般来讲go项目有以下几个目录
$GOPATH/src 项目代码存放目录
$GOPATH/bin 依赖包安装后生成的可执行文件
$GOPATH/pkg
$GOPATH/vender

下面写第一个go程序,看看go的项目代码存放、编译的路径
先创建一个项目 $GOPATH/src/go_dev/day1/example1/
在下面写一个hello world,进行编译
04go8

注意上面的$GOPATH等于Go_work