前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 学习笔记

golang 学习笔记

作者头像
付威
发布2021-05-06 10:56:17
9560
发布2021-05-06 10:56:17
举报

1. go语言的概述和环境配置

2. go语言的语法

第一个go语言程序

代码语言:javascript
复制
package main

import "fmt"

func main() {
	fmt.Println("Hello World")
}

go语言的变量定义

go语言完整的定义的变量的方法为 var 变量名 类型=值var name string ="fuwei",可以简写为name:="fuwei"(这种只能在函数内使用,无法再包内使用),

代码语言:javascript
复制
package main

import "fmt"

func main() {
	var a int=0
	var b=0 //编译器自动猜测
	c:=0
	var d int
	var e string
	f:=""
	arr:=[5]int{1,2,3,4,5}
	var arr1 [5]int

	var slice1 []int
	slice2:=[]int{1,2,3,4,5}
	
	fmt.Println(a,b,c,d,f,e,arr,arr1,slice1,slice2)
	
}

//简单的一个计算demo,勾股定理
func main() {
	a, b := 3, 4
	c := int(math.Sqrt(float64(a*a + b*b)))
	fmt.Print(c)
}

常量的定义,和其他语言一样,常量的值是不可变动的,在不指定常量的类型的时候,编译器会自动转换

代码语言:javascript
复制
func main() {
	a, b := 3, 4
	c := int(math.Sqrt(float64(a*a + b*b)))
	fmt.Print(c)
  
  const e, f = 3, 4
	g := math.Sqrt(float64(e*e + f*f))
	fmt.Print(g)
}

枚举

在go语言中枚举的定义使用const定义。可以直接定义

代码语言:javascript
复制
const (
	Cpp    = 0
	java   = 1
	python = 2
)

使用iota表达式

代码语言:javascript
复制
package main

import "fmt"

const (
	Cpp = iota //代表是自增
	java
	python
)

const (
	b = 1 << (10 * iota) //代表使用1 << (10 * iota)来增加值
	kb
	mb
	gb
	tb
	pb
)

func main() {
	fmt.Println(Cpp, java, python)
	fmt.Println(b, kb, mb, gb, tb, pb)
}

//结果:
0 1 2 
1 1024 1048576 1073741824 1099511627776 1125899906842624

3. 条件和循环

3.1 条件

使用if关键字,不用括号,可以跟多个语句

代码语言:javascript
复制
func main() {
	const fileName = "/Users/fuwei/Documents/笔记/Go语言/01.goLang.md"
	contents, err := ioutil.ReadFile(fileName)
	if err!=nil {
		fmt.Print(err)
	} else {
		fmt.Println(contents)
	}
	fmt.Println(grade(10))

}

if可以简写为

代码语言:javascript
复制

func main() {
	const fileName = "/Users/fuwei/Documents/笔记/Go语言/01.goLang.md"
	//contents, err := ioutil.ReadFile(fileName)
  //这里的修改
	if 	contents, err := ioutil.ReadFile(fileName)
		err != nil {
		fmt.Print(err)
	} else {
		fmt.Println(contents)
	}
	fmt.Println(grade(10))

}

swtich case,不用写break编译器会自动添加

代码语言:javascript
复制

func grade(score int) string {
	//默认
	g := ""
	switch {
	case score < 60:
		{
			g = "F"

		}
	case score < 80:
		{
			g = "F"

		}
	case score < 100:
		{
			g = "A"

		}
	default:
		{
			panic("unknow")

		}

	}
	return g
}

3.2 循环

代码语言:javascript
复制
package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

//07.循环
func main() {
	fmt.Println(convertToBin(10),
		convertToBin(15))
	readFile("/Users/fuwei/Documents/笔记/Go语言/01.goLang.md")
}

func convertToBin(n int) string {
	result := ""
	for ; n > 0; n /= 2 {
		lsb := n % 2
		//转数据
		result = strconv.Itoa(lsb) + result

	}
	return result
}

func readFile(filePath string)  {
	open, err := os.Open(filePath)
	if err != nil {
		panic(err)
	}
	scanner := bufio.NewScanner(open)

	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
}

一个小总结

  1. go语言的变量定义,变量名在前,类型在后
  2. 导入的包一定要使用
  3. 定义的变量一定要使用,如果不想使用可以用_来代替

4. 函数

  1. 返回值类型写在后面
  2. 可返回多个值
  3. 函数作为参数实现函数式编程
  4. 没有默认值,可变参数列表

4.1 函数的定义

标准的函数

代码语言:javascript
复制
//多个返回值
func print(a int){
	 	fmt.Println(a)
}

多个返回值

代码语言:javascript
复制
//多个返回值
func div(a, b int) (q, r int) {
	i, _ := eval(a, b, "/")
	return i, a % b
}

4.2 函数式编程

代码语言:javascript
复制
func apply(op func(float64, float64) float64, a, b float64) float64 {
	//通过反射来
	p := reflect.ValueOf(op).Pointer()
	opName := runtime.FuncForPC(p).Name()
	fmt.Println("Calling function is %s with args (%d,%d)", opName, a, b)
	return op(a, b)
}

4.3 可变参数列表

代码语言:javascript
复制
func sum(nums ...int) int {
	sum := 0
	for i := range nums {
		sum = sum + nums[i]
		fmt.Println(i)
	}
	return sum
}

5. 指针

值传递和引用传递

使用C++的代码来演示传值和传引用的区别

代码语言:javascript
复制
void pass_by_value(int a){
    a++;
}

 void pass_by_ref(int& a){
    a++;
}

int main() {
    void pass_by_value(int);
    void pass_by_ref(int&);
    
    int a=5;
    pass_by_value(a);
    std::cout<<"a:"<<a;
    int b=5;
    pass_by_ref(b);
    std::cout<<"\n b:"<<b;
    return 0;
}

打印结果是 a:5 b:6

go 语言只有值传递,没有引用传递,函数传递的时候都需要拷贝一份。

在GO语言中如何交换两个值?

代码语言:javascript
复制
package main

import "fmt"

func main() {
	a, b := 3, 4
	swap(a, b)

	fmt.Println(a, b)
	swap1(&a, &b)
	fmt.Println(a, b)
}

func swap(a, b int) {
	a, b = b, a
}

//能够交换
func swap1(a, b *int) {
	*a, *b = *b, *a
}

6. 数组

数组是一个定长的数据集合:

数组的定义

代码语言:javascript
复制
var arr1 [5]int
arr2 := [3]int{1, 3, 5}
//让编译器判断
arr3 := [...]int{2, 4, 6, 8, 10}
   
   

多维数组

代码语言:javascript
复制
//多维数组
var grid [4][5]int

数组作为函数传递

代码语言:javascript
复制
//这样是拷贝整个数组,导致内存浪费
func printArr(arr [5]int) {
	fmt.Println("print arr")
	for i, v := range arr {
		fmt.Println(i, v)
	}
}

//这个是地址的拷贝
func printArrp(arr *[5]int) {
	fmt.Println("print arr")
	for i, v := range arr {
		fmt.Println(i, v)
	}
}

7.切片

切片可以理解成动态的数组。

切片生成的对应的数组是原数组的一个视图,如果修改则会影响原来的数据

数组创建的时候,数据都是0。

代码语言:javascript
复制
package main

import "fmt"

//切片
func main() {
	arr3 := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	fmt.Println("arr3[2:5]", arr3[2:5])
	fmt.Println("arr3[:5]", arr3[:5])
	fmt.Println("arr3[2:]", arr3[2:])
	fmt.Println("arr3[:]", arr3[:])
	//fmt.Println("arr3[2:5]",arr3[2:5])

	s1 := arr3[2:6]
	updateArr(s1)
	fmt.Println("s1=", s1)
	fmt.Println(arr3)

	//slice再次建立slice
	s2 := s1[3:7]
	fmt.Println("s2=", s2)
  
  //新的
	s3:= append(s2, 10)
	fmt.Println(s3)
	fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))
  
  //超越cap
	fmt.Println("\nover cap")
	s4:= append(s3, 10,11,23,12,123)
	fmt.Println(s4)
	fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))
}

func updateArr(s []int) {
	s[0] = 100
}
  1. 正常的切片使用
代码语言:javascript
复制
package main
import "fmt"
//切片
func main() {
	arr3 := [...]int{0,1,2,3,4,5,6,7,8,9}
	fmt.Println("arr3[2:5]",arr3[2:5])
	fmt.Println("arr3[:5]",arr3[:5])
	fmt.Println("arr3[2:]",arr3[2:])
	fmt.Println("arr3[:]",arr3[:])
	//fmt.Println("arr3[2:5]",arr3[2:5])
}

切片生成的对应的数组是原数组的一个视图,如果修改则会影响原来的数据

代码语言:javascript
复制
s1 := arr3[2:6]
	updateArr(s1)
	fmt.Println("s1=", s1)
	fmt.Println(arr3)
	

如果切片的子切片越界,也就是slice的扩展,可以向后扩展

代码语言:javascript
复制
//slice再次建立slice,越界
	s2 := s1[3:7]
	fmt.Println("s2=", s2)

切片的长度和容量(len和cap)len是切片的长度,cap是向能向后扩展的长度

代码语言:javascript
复制
fmt.Printf("s1=%v,len(s1)=%v cap(s1)=%v",s1,len(s1),cap(s1))
s1=[100 3 4 5],len(s1)=4 cap(s1)=8

向切片添加数据,添加数据如果大于原来的数组Cap,系统会重新分配更大的数组,原来的数组会被垃圾回收

代码语言:javascript
复制
//新的
s3:= append(s2, 10)
fmt.Println(s3)
fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))
fmt.Println("\nover cap")
s4:= append(s3, 10,11,23,12,123)
fmt.Println(s4)
fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v",arr,len(arr),cap(arr))

Slice cap是2^n的增长

代码语言:javascript
复制
package main

import "fmt"

func main() {
	var arr []int
	for i := 0; i < 100; i++ {
		arr = append(arr, 2*i+1)
		fmt.Printf("arr=%v,len(arr)=%v cap(arr)=%v\n", arr, len(arr), cap(arr))
	}
}

申请切片,指定长度和容量

长度代表需要使用的长度,容量代表真正的大小,如果大于这个容量会从新分配一个新的数组,这样保证了内存使用和内存的申请的一个平衡

代码语言:javascript
复制
//创建slice
	s2:=make([]int ,16)
	s3:=make([]int,10,32)//长度和容量
	fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
	fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))

切片的拷贝,拷贝到dst的数组中。

代码语言:javascript
复制
copy(s2,arr)
fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))

切片的数据删除

代码语言:javascript
复制
//创建slice
s2 := make([]int, 16)
s3 := make([]int, 10, 32) //长度和容量
fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))
   
copy(s2, arr)
fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
fmt.Printf("s3=%v,len(s3)=%v cap(s3)=%v\n", s3, len(s3), cap(s3))
   
// 删除中间的元素
s2 = append(s2[:3], s2[4:]...)
fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))
   
front := s2[0]
s2 = s2[1:]//删除头元素
tail := s2[len(s2)-1]
s2 = s2[:len(s2)-1]//删除尾元素
fmt.Printf("front:%v,tail:%v\n", front, tail)
fmt.Printf("s2=%v,len(s2)=%v cap(s2)=%v\n", s2, len(s2), cap(s2))

8. map结构

创建map,获取元素,判断可以是否存在,删除key,k-v遍历

代码语言:javascript
复制
package main

import "fmt"

func main(){
	m:=map[string]string{
		"key1":"val1",
		"key2":"val2",
		"key3":"val3",
		"key4":"val4",
		"key5":"val5",
	}
	m2:=make(map[string]int) //m2==empty
	var m3 map[string] int  //nil 可以参与安全运算
	fmt.Println(m,m2,m3)
	//map是无序的,每次顺序都不同
	for k,v :=range m{
		fmt.Printf("\n Key:%s,Value:%s\n",k,v)
	}

	fmt.Println(m["key1"])
	fmt.Println(m["key10"])

	//判断是否存在
	if s,hasKey := m["key10"];hasKey{
		fmt.Println(s)
	}else {
		fmt.Println("key not exist")
	}

    delete(m,"key1")
	fmt.Println(m)

}
  • map使用哈希表,必须可以比较相等
  • 除了slice,map,function的内建类型都可以作为key
  • Struct类型不包括上述字段,也可以为key

[实战]

寻找最长不含有除服的字符串的子串

eg. abcabcbb—-> abc , bbbbb—>b, pwwkew—->wke

代码语言:javascript
复制
package main

import "fmt"

func main() {
	str := "abcdacbdacbd"
	fmt.Println(norepeateSubstr(str))
}

func norepeateSubstr(s string) int {
	start := 0
	m := make(map[string]int)
	max := 0
	for i, ch := range []byte(s) {
		if v, hasKey := m[string(ch)]; hasKey && v >= start {
			start = m[string(ch)] + 1
		}
    //当前最大值减去起始值,比较大小
		if i-start+1 > max {
			max = i - start + 1
		}
		m[string(ch)] = i
	}
	return max
}

Eg. goLang处理中文,rune

9. 面向对象

go语言没有构造函数,如果想使用构造的方法,可以手写一个工厂函数。

代码语言:javascript
复制
package main

import "fmt"

type treeNode struct {
	value       int
	left, right *treeNode
}

func main() {
	var root treeNode

	root = treeNode{value: 3}
	root.left = &treeNode{}
	root.right = &treeNode{}
	root.right.left = new(treeNode)
	root.left.right = createNode(2)
	nodes := []treeNode{
		{value: 3}, {}, {6, nil, &root},
	}
	root.left.right.setValue(4)
	root.left.right.print()
	root.left.right.setValuePoint(5)
	root.left.right.print()
	fmt.Println(nodes)

}

//类似构造函数
//此处返回的是局部变量的地址
func createNode(value int) *treeNode {
	return &treeNode{value: value}
}

//代表方法成员方法
func (node treeNode) print() {
	fmt.Println(node.value)
}

//这个方法是传值的,无法修改对象的值
func (node treeNode) setValue(value int) {
	node.value = value
}

//这个方法是传值的,无法修改对象的值
func (node *treeNode) setValuePoint(value int) {
	node.value = value
}

//使用是相同,但是是传值的
func print(node treeNode) {
	fmt.Println(node.value)
}

定义类

代码语言:javascript
复制
type treeNode struct {
	value       int
	left, right *treeNode
}

创建对象

代码语言:javascript
复制
var root treeNode
root = treeNode{value: 3}
root.left = &treeNode{}
root.right = &treeNode{}
root.right.left = new(treeNode)
root.left.right = createNode(2)
nodes := []treeNode{
	{value: 3}, {}, {6, nil, &root},
}

创建构造函数

代码语言:javascript
复制
//类似构造函数
//此处返回的是局部变量的地址
func createNode(value int) *treeNode {
	return &treeNode{value: value}
}

创建成员方法

代码语言:javascript
复制

//代表方法成员方法
func (node treeNode) print() {
	fmt.Println(node.value)
}

//这个方法是传值的,无法修改对象的值
func (node treeNode) setValue(value int) {
	node.value = value
}
func (node *treeNode) setValuePoint(value int) {
	node.value = value
}

树的遍历

代码语言:javascript
复制

func(node *treeNode) traverse(){
	if node==nil{
		return
	}
  //此处不用判断nil 因为nil也可以使用方法
	node.left.traverse()
	node.print()
	node.right.traverse()
}

10. 封装

  1. 名字风格使用CamelCase
  2. 首字母大写:public,首字母小写:private
  3. 最小的单位是包

10.1 包

  1. 一个目录一个包
  2. main包中才有可执行入口
  3. 为结构定义的方法必须放在同一个包里,可以是不同的文件

TreeNode的修改

目录结构:

代码语言:javascript
复制
├── main.go
├── tree
    └── Node.go

代码如下:

代码语言:javascript
复制
package tree

import "fmt"

type Node struct {
	Value       int
	Left, Right *Node
}


//类似构造函数
//此处返回的是局部变量的地址
func CreateNode(value int) *Node {
	return &Node{Value: value}
}

//代表方法成员方法
func (node Node) Print() {
	fmt.Println(node.Value)
}

//这个方法是传值的,无法修改对象的值
func (node Node)SetValue(value int) {
	node.Value = value
}
func (node *Node) SetValuePoint(value int) {
	node.Value = value
}

//使用是相同,但是是传值的
func print(node Node) {
	fmt.Println(node.Value)
}

func(node *Node) Traverse(){
	if node==nil{
		return
	}
	node.Left.Traverse()
	node.Print()
	node.Right.Traverse()
}

main方法:

代码语言:javascript
复制
package main

import "fmt"
import "./tree"

func main() {
	var root tree.Node
	root = tree.Node{Value: 3}
	root.Left = &tree.Node{}
	root.Right = &tree.Node{}
	root.Right.Left = new(tree.Node)
	root.Left.Right = tree.CreateNode(2)
	nodes := []tree.Node{
		{Value: 3}, {}, {6, nil, &root},
	}
	root.Left.Right.SetValue(4)
	root.Left.Right.Print()
	//调用方式不发生改变,
	//编译器会根据方法的参数来传递决定传地址还是值。
	root.Left.Right.SetValuePoint(5)
	root.Left.Right.Print()
	fmt.Println(nodes)
	root.Traverse()
}

10.2对象的扩展

代码语言:javascript
复制
package treeext

import "../tree"

type NodeEx struct {
	Node *tree.Node
}

func (nodeEx *NodeEx) Traverse() {
	if nodeEx == nil || nodeEx.Node == nil {
		return
	}

	nodeEx.Node.Right.Traverse()
	nodeEx.Node.Print()
	nodeEx.Node.Left.Traverse()
}

10.3 扩展一个队列

代码语言:javascript
复制
package queue
type Queue []int
func (q *Queue) Push(value int) {
	*q = append(*q, value)
}

func (q *Queue) Pop() int {
	p := (*q)[0]
	*q=(*q)[1:]
	return p
}

11. 接口

go语言的接口比较灵活,不用显示的实现

duck typing

接口的定义

代码语言:javascript
复制
type Retriever interface {
	Get(url string) string
} 

接口的实现 ,不需要显示的定义实现哪个接口

代码语言:javascript
复制
package retriever

type MockRetriever struct {
}

func (mockRetiever MockRetriever) Get(url string) string {
	return "get baidu ok"
}
代码语言:javascript
复制
package retriever

import (
	"net/http"
	"net/http/httputil"
)

type HttpRetriever struct{}

func (retriever *HttpRetriever) Get(url string) string {
	content, err := http.Get(url)
	if err != nil {
		panic(err)
	}
	defer  content.Body.Close()
	response, err := httputil.DumpResponse(content,true)
	if err!=nil{
		panic(err)
	}
	return string(response)
}
  1. 接口的组合
  2. 接口内部有什么,接口的内容

12. 资源的管理

Defer 函数执行完成后释放,有panic和return 也不受影响

代码语言:javascript
复制

package main

import (
	"fmt"
)

func main() {
	tryDefer()
}

func tryDefer() {
	defer fmt.Println(1)
	defer fmt.Println(2)

	fmt.Println(3)
	panic("error")
	fmt.Println(4)
	return
}
  //打印结果;
  3
  2
  1

错误处理

代码语言:javascript
复制
   

统一的错误处理

简单的文件服务器:

代码语言:javascript
复制
package main

import (
	"io/ioutil"
	"net/http"
	"os"
)

type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
	return func(writer http.ResponseWriter, request *http.Request) {
		err:=handler(writer,request)
		if err!=nil{
			switch  {
			case os.IsNotExist(err):
				http.Error(writer,http.StatusText(http.StatusNotFound),http.StatusNotFound)

			}
		}
	}
}

func main() {
	http.HandleFunc("/List/", errWrapper(fileList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {

	}
}

func fileList(writer http.ResponseWriter, request *http.Request) error {

	path := request.URL.Path[len("/List/"):]
	file, err := os.Open(path)
	if err != nil {
		//http.Error(writer, err.Error(), http.StatusInternalServerError)
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)
	if err != nil {
		panic(err)
	}
	writer.Write(all)
	return err
}

12. 探讨指针的专栏

13. panic和recover

panic

相当于throw

  1. 停止当前函数执行
  2. 一直向上返回
  3. 没有遇见recover,程序退出

recover

  1. 仅在defer中调用
  2. 获取panic的值
  3. 如果无法处理,可以重新panic
代码语言:javascript
复制
package main

import "fmt"

func main() {
	tryRecover()
	
}

func tryRecover() {
	defer func() {
		r:=recover()
		if err,ok:=r.(error);ok{
			fmt.Println("进入自定义错误处理:")
			fmt.Println(err)
		}else {
			panic(r)
		}
	}()
	b:=0
	a:=5/b
	fmt.Println(a)
}

学习到底32讲,做一个整理

14. routine

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func main() {
	var arr [10]int
	for i := 0; i < 10; i++ {
		go func(i int) {
			for {
				arr[i]++
				//主动交出控制权
				//runtime.Gosched()
			}
		}(i)
	}
	time.Sleep(time.Second)
	fmt.Println(arr)
}
代码语言:javascript
复制
go func(i int) {
			for {
				arr[i]++
				//主动交出控制权
				//runtime.Gosched()
			}
		}(i)
此处使用闭包的传值的方法

14.1 Coroutine

14.2 Channel

  1. 基于CSP模型实现并发,
  2. 不要通过共享内存来通信,通过通信来共享内存
    1. 所有的channel的收发都是阻塞的
    2. 定义使用var c chan int,c:=make(chan int)
    3. 如果定义一个chan没有接收的对象,则会造成死锁
    4. 如果chan有缓存区,但是数量超过了缓存区,也会造成deadlock
代码语言:javascript
复制
package main

import (
	"fmt"
)

func chandemo1() {
	c := make(chan int)
	c <- 1 //这里会阻塞,如果没有发出,就会造成死锁
	c <- 2
	n := <-c
	n = <-c
	fmt.Println(n)

}
//会报错

因为chan的输入和输出的值的过程的阻塞的,如果没有发出,就会造成程序永久的阻塞,造成死锁问题.

为了解决这个问题,需要再开一个协程来处理数据chan的发出

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func chanDemo() {
	c := make(chan int)
	go func() {
		for {
			n := <-c
			fmt.Println(n)
		}
	}()
	c <- 1
	c <- 2
	time.Sleep(time.Millisecond)
}
func main() {
	chanDemo()
}
代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func work(id int, c chan int) {
	for {
		fmt.Printf("work %d,do %c\n", id, <-c)
	}
}
func chanDemo() {
	var channels [10]chan int

	for i := 0; i < 10; i++ {
		channels[i] = make(chan int)
		go work(i, channels[i])
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)
}
func main() {
	chanDemo()
}

chan int 作为返回值

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func createWork(id int) chan int {
	c := make(chan int)
	go func() {
		for {
			fmt.Printf("work %d,do %c\n", id, <-c)
		}
	}()
	return c
}
func chanDemo() {
	var channels [10]chan int

	for i := 0; i < 10; i++ {
		channels[i] =createWork(i)

	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)
}
func main() {
	chanDemo()
}

定义chan只能发数据或者收数据

chan <-代表只能收数据和<-chant外面只能获得数据,如果使用错误会导致编译失败

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

func createWork(id int) chan<- int {
	c := make(chan int)
	go func() {
		for {
			fmt.Printf("work %d,do %c\n", id, <-c)
		}
	}()
	return c
}
func chanDemo() {
	var channels [10]chan<- int

	for i := 0; i < 10; i++ {
		channels[i] =createWork(i)

	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i
	}
	for i := 0; i < 10; i++ {
		channels[i] <- 'A' + i
	}
	time.Sleep(time.Millisecond)
}
func main() {
	chanDemo()
}

chan增加缓冲区

协程虽然是轻量级的资源,但是发送完成后,立即进行资源的切换也会比较消耗资源,所以可以定义一个缓冲区,先收完数据再进行发送。

代码语言:javascript
复制
c := make(chan int, 3)//定义缓冲区长度为3
代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)
func  work(id int,c chan int){
	for {
		fmt.Printf("work %d,do %c\n", id, <-c)
	}
}
func bufferedChannel() {
	c := make(chan int, 3)
	go work(0,c)

	c <- 'a'
	c <- 'b'
	c <- 'c'
    c <- 'd'
	time.Sleep(time.Millisecond)
}
func main() {
	//chanDemo()
	bufferedChannel()
}

如果知道数据已经发完了?

告知发送三个值后我已经发送完了,在接受方还要定义已经接受完的特殊值:

代码语言:javascript
复制
func finishSend() {
	c := make(chan int, 3)
	go work(0, c)

	c <- 'a'
	c <- 'b'
	c <- 'c'
	close(c)
	time.Sleep(time.Millisecond)
}

func work(id int, c chan int) {
	for {
		i,ok:= <-c
		if !ok{
      //如果没有收到值,直接不接收数据了,因为此处是个死循环
			break
		}
		fmt.Printf("work %d,do %c\n", id, i)
	}
}
//或者使用rang函数
func work(id int, c chan int) {
	for i := range c {
		fmt.Printf("work %d,do %c\n", id, i)
	}
}

43. 通过通信共享内存

代码语言:javascript
复制
package main

import (
	"fmt"
)

type worker struct {
	in   chan int
	done chan bool
}

func work(id int, c chan int, done chan bool) {
	for i := range c {
		fmt.Printf("work %d,do %c\n", id, i)
		done <- true
	}
}
func createWork(id int) worker {
	w := worker{
		in:   make(chan int),
		done: make(chan bool),
	}
	go work(id, w.in, w.done)
	return w
}
func chanDemo() {
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWork(i)
	}
	for i, worker := range workers {
		worker.in <- 'a' + i
	}
	for i, worker := range workers {
		worker.in <- 'A' + i
	}
	for _, worker := range workers {
		<-worker.done
		<-worker.done
	}
	//time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
	//bufferedChannel()
}

当打印完小写的时候会产生报错,报错的原因是work的方法返回值没有接收。

sync.WaitGroup的使用,类似java中的CountDownLatch,可以动态追加

代码语言:javascript
复制
package main

import (
	"fmt"
	"sync"
)

type worker struct {
	in chan int
	wg *sync.WaitGroup
}

func work(id int, c chan int,wg *sync.WaitGroup) {
	for i := range c {
		fmt.Printf("work %d,do %c\n", id, i)
		wg.Done()
	}
}
func createWork(id int,wg *sync.WaitGroup) worker {
	w := worker{
		in: make(chan int),
	}
	go work(id, w.in,wg)
	return w
}
func chanDemo() {
	var wg sync.WaitGroup
	var workers [10]worker
	for i := 0; i < 10; i++ {
		workers[i] = createWork(i,&wg)
	}
	for i, worker := range workers {
		worker.in <- 'a' + i
		wg.Add(1)
	}

	for i, worker := range workers {
		worker.in <- 'A' + i
		wg.Add(1)
	}

	wg.Wait()
	//time.Sleep(time.Millisecond)
}

func main() {
	chanDemo()
	//bufferedChannel()

}

45. select

如果两个channel c1,c2,谁先获得值,就采用的谁的数据

代码语言:javascript
复制
package main

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

func main() {
	var c1, c2 = generator(), generator()
	var worker = createWork(0)
	n := 0
	var values []int
	//产生一个channel 到时间会送入一个值
  //此处使用10s
	after := time.After(10 * time.Second)
	tick := time.Tick(time.Second)
	fmt.Println("开始时间:", time.Now())
	for {
		var activeWorker chan<- int
		var activeValue int
		if len(values) > 0 {
			activeWorker = worker
			activeValue = values[0]
		}

		select {

		//缓存起来排队
		case n = <-c1:
			{
				values = append(values, n)
			}
		case n = <-c2:
			{
				values = append(values, n)
			}
			//负责打印
		case activeWorker <- activeValue:
			{
				values = values[1:]
			}
		case <-time.After(800 * time.Millisecond):
			{
				fmt.Println("timeout")
			}
		case <-tick:
			{
				fmt.Println("queue len:", len(values))
			}
			//到时间退出
		case <-after:
			{
				fmt.Println("到时间结束:", time.Now())
				return
			}
			//非阻塞
		default:
			{

			}
		}

	}
}
func work(id int, c chan int) {

	for i := range c {
		time.Sleep(time.Second)
		fmt.Printf("work %d,do %d\n", id, i)
	}
}
func createWork(id int) chan<- int {
	c := make(chan int)
	go work(id, c)
	return c
}
func generator() chan int {
	out := make(chan int)
	go func() {
		i := 0
		for {
			fmt.Println("生成数字", i)
			time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
			out <- i
			i++
		}
	}()
	return out
}

46 传统的互斥锁

代码语言:javascript
复制
package main

import (
	"fmt"
	"time"
)

type atomicInt int

func (a *atomicInt) increment() {
	*a++
}

func (a *atomicInt) get() int {
	return int(*a)
}

func main() {
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Second)
	fmt.Println(a)
}

使用go run -race main.go查看执行的轨迹

代码语言:javascript
复制
==================
WARNING: DATA RACE
Read at 0x00c000134008 by main goroutine:
  main.main()
      /Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:25 +0xc5

Previous write at 0x00c000134008 by goroutine 7:
  main.(*atomicInt).increment()
      /Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:11 +0x51
  main.main.func1()
      /Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:22 +0x32

Goroutine 7 (finished) created at:
  main.main()
      /Users/fuwei/work/goPros/go/learnGo/bytedance/ch46.mutex/main.go:21 +0xaa
==================
2
Found 1 data race(s)
exit status 66

增加sync.Mutex对操作加锁

代码语言:javascript
复制
package main

import (
	"fmt"
	"sync"
	"time"
)

type atomicInt struct {
	value int
	lock  sync.Mutex
}

func (a *atomicInt) increment() {
	a.lock.Lock()
	defer a.lock.Unlock()
	a.value++
}

func (a *atomicInt) get() int {
	a.lock.Lock()
	defer a.lock.Unlock()
	return a.value
}

func main() {
	var a atomicInt
	a.increment()
	go func() {
		a.increment()
	}()
	time.Sleep(time.Second)
	fmt.Println(a.get())
}

47. http

文件服务器增加pprof

代码语言:javascript
复制
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
	//避免没有的时候 报错
	_ "net/http/pprof"
	"os"
)

type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
	return func(writer http.ResponseWriter, request *http.Request) {
		err := handler(writer, request)
		if err != nil {
			switch {
			case os.IsNotExist(err):
				http.Error(writer, http.StatusText(http.StatusNotFound), http.StatusNotFound)
			}
		}
	}
}

func main() {
	http.HandleFunc("/file/", errWrapper(fileList))
	http.HandleFunc("/List/", errWrapper(getDirList))
	err := http.ListenAndServe(":8888", nil)
	if err != nil {

	}
}
func getDirList(writer http.ResponseWriter, request *http.Request) error {
	dir_list, e := ioutil.ReadDir("./")
	if e != nil {
		fmt.Println("read dir error")
		return e
	}

	for _, v := range dir_list {
		bytes := []byte(v.Name() + "\n")
		writer.Write(bytes)
	}
	return e
}
func fileList(writer http.ResponseWriter, request *http.Request) error {

	path := request.URL.Path[len("/List/"):]
	file, err := os.Open(path)
	if err != nil {
		//http.Error(writer, err.Error(), http.StatusInternalServerError)
		return err
	}
	defer file.Close()
	all, err := ioutil.ReadAll(file)
	if err != nil {
		panic(err)
	}
	writer.Write(all)
	return err
}

访问localhost:8888/debug/pprof,显示当前的程序运行的堆栈:

go tool pprof http://localhost:8888/debug/pprof/profile使用这个命令获得30s的cpu的运行情况

碰到的故障:failed to execute dot. Is Graphviz installed? Error: exec: "dot": executable file not found in $PATH 是电脑没有安装gvedit导致

48. 常用的第三方库

  1. bufio
  2. log
  3. Encoding/json
  4. regexp
  5. time
  6. strings/math/rand

查看文档godoc -http :8888

49. 分布式爬虫

更新中…

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-012,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. go语言的概述和环境配置
  • 2. go语言的语法
  • 3. 条件和循环
    • 3.1 条件
      • 3.2 循环
      • 一个小总结
      • 4. 函数
      • 4.1 函数的定义
        • 4.2 函数式编程
          • 4.3 可变参数列表
          • 5. 指针
          • 6. 数组
          • 7.切片
          • 8. map结构
          • 9. 面向对象
          • 10. 封装
            • 10.1 包
              • 10.2对象的扩展
                • 10.3 扩展一个队列
                • 11. 接口
                  • duck typing
                  • 12. 资源的管理
                  • 12. 探讨指针的专栏
                  • 13. panic和recover
                    • panic
                      • recover
                      • 14. routine
                        • 14.1 Coroutine
                          • 14.2 Channel
                          • 45. select
                            • 46 传统的互斥锁
                              • 47. http
                                • 48. 常用的第三方库
                                • 49. 分布式爬虫
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档