ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

golang速成教程

2022-01-08 19:06:09  阅读:256  来源: 互联网

标签:教程 string int fmt 速成 golang var Println os


1、go语言概述

go核心编程方向:

  • 区块链研发工程师
  • go服务器端/游戏软件工程师
  • go分布式/云计算软件工程师

go的优势:

  • 数据处理
  • 高并发

google为什么要创造go语言:

  • 硬件技术更新频繁,性能提高很快,现有语言不能合理利用多核多CPU的优势
  • 现有语言计算能力不够,处理大并发不够好
  • 想兼顾运行速度和开发速度

发展简史:

  • 2015年,go1.5版本发布,移除了最后残余的c代码
  • 2017年,先后发布了go1.8和1.9
  • 2018年,发布了go1.10版本

go的特点:

  • go=c+python
  • 从c语言继承了很多理念,如指针
  • 自动垃圾回收
  • 天然并发
    • 从语言层面支持并发
    • goroutine,轻量级线程,可实现大并发处理,高效利用多核
    • 基于CPS并发模型实现
  • 通过管道实现不同的goroute之间的相互通信
  • 函数可以返回多个值
  • 新的语法:延时执行defer、切片slice等

2、基础语法

1、变量使用方式

  1. 指定变量类型,声明后若不赋值,使用默认值
  2. 根据值自动推导类型
  3. 连var也一起省略,但要使用:=符,等价于声明+赋值
var i int
fmt.Println("i=",i)

var j = "hello"
fmt.Println("j=",j)

k := 10.5
fmt.Println("k=",k)

注意:main函数要放在main包下面才能运行,如下图所示。

在这里插入图片描述

2、一次性声明多个变量

	var i,j,k int
	fmt.Println("i=",i,"j=",j,"k=",k)

	var o,p,q = 10,"hello",10.5
	fmt.Println("o=",o,"p=",p,"q=",q)

	r,s,t := 10,"hello",10.5
	fmt.Println("r=",r,"s=",s,"t=",t)

3、声明全局变量

package main

var n1 = 100
var n2 = "okk"
var (
	n3 = 36.9
	n4 = "yes"
)

func main() {
}

4、常量

const a int = 10

常量必须初始化

常量只能修饰bool、数值类型(int、float系列)、string类型

5、go的数据类型

基本数据类型

  • 数值型
    • 整数类型(int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64)
    • 浮点类型(float32,float64)
  • 字符型(没有专门的字符型,使用byte来保存单个字母字符)
  • 布尔型(bool)
  • 字符串

派生/复杂数据类型

  • 指针
  • 数组
  • 结构体
  • 管道
  • 函数
  • 切片(动态数组)
  • 接口
  • map

6、值类型和引用类型

值类型:基本数据类型、数组、结构体

引用类型:指针、切片、map、管道、interface

7、运算符

golang的自增和自减只能当做独立语句使用,不能b:=a++

golang没有++i

3、go字符与字符串

1、go字符的不同之处

对于其它语言来说,字符串是由一串字符组成的,而go的字符串是由一串字节组成的。

    var b1 byte = 'a'
	fmt.Println("b1=",b1) // 输出97

	var b2 byte = 97
	fmt.Printf("b2=%c\n",b2) // 输出a

	b3 := '你'
	fmt.Println("b2=",b3) // 输出unicode码值

下面测试字符和布尔类型分别占用几个字节。

b := 'a'
success := false

fmt.Println(unsafe.Sizeof(b)) // 输出4
fmt.Println(unsafe.Sizeof(success)) // 输出1

如果一个字符的UTF-8编码的字节数大于1,则不能用byte去存,而用int存就没问题。如下图所示。

在这里插入图片描述

2、字符串的基本使用

    var s1 = "hello"+"world" // 拼接
	s1+="hi"
	// 由于go可以不以分号结尾,因此在字符串拼接语句太长需要换行时,每行的结尾必须以+号结尾
	s2 := "我是"+"一个"+
		"中国人"+"hello"+
		"world"
	s3 := `将字符串按原样输出,适用于输出源代码等。\n\t`
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(s3)

3、基本数据类型之间的显示类型转换

    var a int = 9999
	var b byte = byte(a)
	fmt.Println(b)

4、String和基本类型的相互转换

基本类型转string,第一种方法,如下代码所示。

    var a int = 100
	s := fmt.Sprintf("%d", a) // 使用Sprintf函数进行转换
	fmt.Printf("type=%T,value=%q",s,s) // 输出s的类型和值,%q比%s的输出结果多一个双引号

基本类型转string,第二种方法,如下代码所示。

    var a int = 100
	s := strconv.FormatInt(int64(a), 2)
	fmt.Printf("type=%T,value=%q",s,s)

	// f表示格式,10表示小数位保留10位,64表示这个小数是float64
	s = strconv.FormatFloat(12.99, 'f', 10, 64)
	fmt.Printf("type=%T,value=%q",s,s)

string转为基本类型,如下代码所示。

    b, err := strconv.ParseBool("fals1")
	// b, _ := strconv.ParseBool("true") // 如果不想获取err信息则使用_
	fmt.Printf("%v",b) // 如果不能转换成一个有效的值,则转为默认值,string转为其它类型也一样
	fmt.Println(err)

4、获取用户输入

	// 方式1
	var name string
	var age int
	// fmt.Scanln(&name)
	// fmt.Println("你的姓名是:",name)

	// 方式2
	fmt.Scanf("%s %d",&name,&age)
	fmt.Println("你的姓名是:",name,",你的年龄是:",age)

5、生成随机数

	// 设置种子
	rand.Seed(time.Now().Unix())
	n:=rand.Intn(100)+1
	fmt.Println(n)

6、指针

    // 输出基本类型的地址
	i := 10
	fmt.Println(&i)

	// 声明一个指针,类型是*int,且p本身的值是&i
	var p *int = &i
	fmt.Println(p) // 输出p本身的值
	fmt.Println(&p) // 输出p的地址
	fmt.Println(*p) // 输出p指向的值

7、流程控制语句

1、if和switch

	if 5>3 {
		fmt.Println("你好")
	}

	// 支持在if中定义变量
	if age:=10;age>9 {
		fmt.Println("hello")
	}

	score:=30
	switch score {
	case 10,20: fmt.Println("差劲") // 自动带break
	case 30,40: fmt.Println("还行"); fallthrough // 默认只穿透一层
	default: fmt.Println("走3")
	}

	// 这种写法跟if-else差不多
	switch {
	case score<10 || score>90:fmt.Println("奇葩")
	default:fmt.Println("中规中矩")
	}

	var service interface{}
	var y = 10
	service = y
	switch service.(type) {
	case nil:fmt.Println("空类型")
	case int:fmt.Println("int类型")
	case float64:fmt.Println("float64类型")
	}

2、for循环

    for i := 1; i < 10; i++ {
		fmt.Println("hello")
	}
	// 相当于while循环,go中没有while关键字
	j:=1
	for j<10{
		fmt.Println("hi")
		j++
	}
	// 无限循环的两种写法
	// for ;;{} 或 for{}

	// for-range,可遍历字符串和数组
	// 传统方式遍历字符串,按字节遍历,不能含中文
	s:="hello,world中"
	for i:=0;i<len(s);i++{
		fmt.Printf("%c\n",s[i])
	}
	// for-range是按字符遍历的
	for index,item:=range s{
		fmt.Printf("%d-%c\n",index,item)
	}
	// 将string转为切片也可以实现按字符遍历
	s2 := []rune(s)
	for i:=0;i<len(s2);i++{
		fmt.Printf("%c\n",s2[i])
	}

3、goto语句

	label:
	fmt.Println("goto test1")
	fmt.Println("goto test2")
	goto label

8、函数

1、函数核心

语法:

func 函数名 (形参列表) (返回值类型列表){
}

注意点:

  • 在接收多个返回值时,可以用占位符_忽略某个返回值。

  • 如果返回值只有一个,返回值类型列表可以不写。

  • go函数不支持重载。

  • go函数的参数传递是值传递,如果希望在函数内修改变量值,则可以传入变量地址,在函数内用指针操作变量。

函数也是一种数据类型,可以赋值给变量,函数可以作为形参。如下代码所示。

	// 定义匿名函数,并赋值给变量
	mysum := func(a int,b int) int {
		return a+b
	}
	fmt.Printf("调用mysum[%T][%d]",mysum,mysum(10,20))

自定义数据类型:type myint int、type mysum func(int,int) int,相当于取别名。如下代码所示。(这个语法用处不大)

	type lensum func(s1 string,s2 string)int
	var totallen lensum
	totallen = func(s1 string,s2 string) int {
		return len(s1)+len(s2)
	}
	fmt.Println("自定义类型:",totallen("hello","hi"))

也支持给函数返回值命名,如下代码所示。(这个语法用处不大)

func sum(a int,b int)(sum int,sub int){
	sum = a+b
	sub = a-b
	return
}

可变参数:

// args是切片,可以通过索引访问
func sum2(args... int) int {
	var sum int
	for i := 0; i < len(args); i++ {
		sum +=args[i]
	}
	return sum
}

初始化函数:

// 每个源文件都可以定义一个init函数,在main函数前执行,被go运行框架调用
func init(){
   fmt.Println("Test_07_function.go初始化了")
}

函数闭包:

	initClickCnt := func(initCnt int) func(int){
		totalCnt := initCnt
        // totalCnt和cnt构成了函数的闭包,totalCnt只初始化一次,相当于全局变量,而cnt相当于局部变量
		return func(cnt int) {
			totalCnt += cnt
			fmt.Println("当前点击次数:",totalCnt)
		}
	}
	printClickCnt := initClickCnt(100)
	printClickCnt(8) // 108
	printClickCnt(10) // 118
	printClickCnt(2) // 120	

defer延迟执行:

	deferTest := func() {
		// 相关语句进入defer栈中,待方法结束后,以后进先出的方式执行defer栈中的语句。可用于关闭文件句柄、数据库连接、锁等资源。
		defer fmt.Println("关闭某资源")
		defer fmt.Println("关闭某句柄")
		fmt.Println("执行逻辑")
	}
	deferTest()

2、系统函数

时间和日期相关函数:

	now := time.Now() // 获取当前时间
	fmt.Printf("%02d-%02d-%02d %02d:%02d:%02d\n",now.Year(),now.Month(),now.Day(),now.Hour(),
		now.Minute(),now.Second())
	fmt.Println(now.Format("2006-01-02 15:04:05")) // 必须要这样写
	time.Sleep(3*time.Second) // 休眠3秒

3、内置函数

len() 求字符串、数组长度

new() 用来分配内存,主要用来分配值类型(填充默认值),返回的是指针。如下代码所示。

	p := new(int)
	fmt.Printf("类型[%T]指针值[%v]指针地址[%v]指针指向的值[%v]指针指向的值类型[%T]",p,p,&p,*p,*p)	

make() 用来分配内存,主要用来分配引用类型,返回的是指针

9、go语言规范

1、包规范

在这里插入图片描述

如果想要编译成一个可执行文件,需要有main包,如果只是写一个库,则可以不需要main包。

在gopath目录下执行如下命令编译成可执行文件:

// 编译时需要编译main包所在的文件夹
// 编译后生成的可执行文件再gopath目录下,也可以通过-o参数指定路径和文件名
go build -o bin/my.exe project_name/package_name/main

在import包时,路径从GOPATH/src开始写,不用带src。

在main包下面有多个main函数,在编译时如何忽略?

  • 只需要在要忽略的main函数所在文件顶部加上// +build ignore 并且至少留一个空行即可。

2、标识符规范

包名尽量和目录名一致

如果变量名、函数名、常量名首字母大写,则为public,否则为private

10、go语言异常处理

func main() {
	// 测试异常处理
	test()
	// 自定义错误
	customError := func(s string) error{
		if(s == "小明"){
			return nil
		}else{
			return errors.New("参数值不是小明!")
		}
	}
	if err := customError("小张");err!=nil {
		panic(err)
	}
}

func test() {
	defer func() {
		if err:=recover();err != nil{
			fmt.Println("test方法发生了异常")
		}
	}() // 写()是为了要执行它
	a:=10
	b:=0
	c:=a/b
	fmt.Println(c)
}

11、数组与切片

1、数组

	// 定义数组的几种方式
	// 方式1
	var arr [5]int
	arr[0] = 2
	fmt.Println(arr[0])
	// 方式2
	var arr2 = [5]int{1,2,3,4,5}
	fmt.Println(arr2[0])
	// 方式3
	var arr3 = [...]int{1,2,3}
	fmt.Println(arr3[0])
	//方式4,如果写...则为数组类型,否则为切片
	var arr4 = [...]string{1:"关羽",0:"刘备",2:"郑飞"}
	fmt.Printf("%T",arr4)

2、切片

	// 定义切片
	// 方式1:引用数组。如果startIndex为0或endIndex为数组长度则可以省略,如arr[:]
	var slice []int = arr2[1:4]
	fmt.Println("切片的值:",slice)
	fmt.Println("切片的元素个数:",len(slice))
	fmt.Println("切片的容量:",cap(slice))
	// 方式2
	var slice2 []int = make([]int,4,16)
	fmt.Println("切片的值:",slice2)
	// 方式3:直接引用数组
	var slice3 []int = []int{1,2,3,4,5}
	fmt.Println("切片的值:",slice3)
	// 切片动态增长
	slice3 = append(slice3,6)
	fmt.Println("切片的值:",slice3)
	// 切片拷贝(数组不行)
	var slice4 []int = make([]int,10,10)
	copy(slice4,slice3)
	fmt.Println(slice4)

3、string与slice

string底层是byte数组,因此也可以切片处理

	// 字符串转成切片,也可以转成[]byte
	s := "hello中国"
	slice5 := []rune(s)
	for _,item:=range slice5 {
		fmt.Printf("%c",item)
	}

4、多维数组

	var arrtwo [3][4]int = [3][4]int{{1,2,3,4},{5,6,7,8},{9,10,11,12}}
	fmt.Println("\n",arrtwo)

12、Map

key的类型:

  • 可以的:bool、数字、string、指针、channel、接口、结构体、数组等
  • 不可以:slice、map、function,因为这几个没法用==判断
	// map可以动态扩容
	var wordcnt map[string]int = make(map[string]int,10)
	wordcnt["关羽"] = 20

	// 声明时就初始化
	var wordcnt2 map[string]int = map[string]int{
		"张飞":20,
		"赵云":16,
		"曹操":32,
	}
	fmt.Println(wordcnt2)

	//删除一个key,如果想删除所有key,则可以用make函数分配新内存
	delete(wordcnt2,"张飞")

	// 查找
	value,b := wordcnt2["曹操"]
	fmt.Println(value,b)

	// 遍历,只能用for-range
	for k,v:=range wordcnt2 {
		fmt.Println(k,"=",v)
	}

	// map切片
	var mapslice []map[string]int = make([]map[string]int,2)
	// 动态增加
	element := map[string]int{
		"hello":5,
		"ok":8,
	}
	mapslice = append(mapslice,element)
	fmt.Println(mapslice)

13、go语言面向对象编程

1、结构体struct

注意点:

  • struct与class类似。
  • go面向对象编程非常简洁,没有继承、方法重载、构造函数、析构函数、隐藏的this指针等。
  • go仍然有封装、继承、多态,只不过实现方式不一样,比如继承没有extends关键字,继承通过匿名字段(组合)实现。
  • 面向接口编程是go很重要的特性。
  • 结构体属性的地址是连续分配的
  • 两个不同的结构体做类型转换时,需要对应的字段全部相同
  • 可以给每个字段起一个tag,在序列化时会用到
  • 由于go的方法作用在指定数据类型上,因此不仅是struct,int、float等也可以有方法
  • 类型可以实现string方法(等同于Java中的toString方法)
type User struct {
	name string `json:"name"`
	age int `json:"age"`
	password string `json:"password"`
}

func (u *User) addAge(b int) {
	// 结构体是值类型,在参数传递时是值拷贝,因此不能修改原struct的属性
	// 如果想修改原struct的属性,需要传入指针,即*User,且底层做了优化,可以用u.age代替(*u).age
	u.age = u.age+b
}

func main() {
	// 方式1:声明结构体变量并赋值
	var u1 User = User{"张三",10,"123456"}
	fmt.Println(u1)
	// 方式2:
	var u2 *User = new(User)
	(*u2).name = "李四"
	u2.password = "ok" // 这样写也可以是因为:在底层做了转换
	fmt.Println(*u2)
	// 方式3
	var u3 *User = &User{}
	u3.name="王五"

	// 调用方法
	u1.addAge(5)
	fmt.Println(u1.age)
}

2、继承

继承通过匿名字段实现。

type Animal struct {
	name string
	age int
}
type Dog struct {
	Animal
	owner string
}

func main() {
	dog := Dog{}
	dog.owner = "张三"
	dog.name = "拉布拉多"
	dog.age = 1
	fmt.Println(dog)
}

注意点:

  • 可继承所有字段和方法,包括私有
  • 当父子含有相同名称的字段或方法时次,采用就近原则
  • 当多重继承时,且多个父结构体之间存在同名字段或方法,则必须指定是哪个父结构体

3、接口与多态

type UserService interface {
	get() string
	insert() int
}

type UserServiceImpl struct {

}

func (service UserServiceImpl) get() string {
	fmt.Println("执行get方法")
	return "name:张三;age:10"
}
func (service UserServiceImpl) insert() int {
	fmt.Println("执行insert方法")
	return 8
}

func doService(service UserService) {
	fmt.Println(service.get())
	fmt.Println(service.insert())
}
func main() {
	doService(UserServiceImpl{})
}

注意点:

  • go没有implements关键字
  • 实现接口只需要一个自定义类型,然后实现接口里面的所有方法即可
  • 空接口interface{}没有任何方法,因为认为所有类型实现了该接口,相当于Java总的Object类
  • 接口中不能定义变量
  • 接口可以继承
  • go中多态是通过接口实现的,支持多态参数和多态数组

4、类型断言与强转的区别

	// 类型断言与强转的区别
	var b UserService
	var a interface{} = UserServiceImpl{}
	// 强转。报错,因为不知道a的具体类型,因此无法强转
	// b = UserService(a) //
	// 类型断言
	b = a.(UserService)
	doService(b)
	// 带检测的类型断言
	c,ok:=a.(UserService)
	if ok {
		doService(c)
	}

14、文件操作

1、基本使用

// 打开文件
	file, _ := os.OpenFile("F:/tmp/test.txt",os.O_RDONLY,os.ModePerm)
	// 关闭文件
	defer file.Close()
	// 循环读取文件
	reader := bufio.NewReader(file)
	for{
		line, err2 := reader.ReadString('\n')
		if err2 == io.EOF {
			// 表示读到了文件末尾
			break
		}
		fmt.Println(line)
	}
	
	// 一次性读取文件
	content, _ := ioutil.ReadFile("F:/tmp/test.txt")
	fmt.Println(content)

	// 写文件
	writer := bufio.NewWriter(file)
	writer.WriteString("hello")
	writer.Flush()
	
	// 判断文件是否存在
	_, err := os.Stat("F:/tmp/test.txt")
	if err == nil {
		fmt.Println("存在")
	}else if os.IsNotExist(err){
		fmt.Println("不存在")
	}else{
		fmt.Println("不确定")
	}
	
	// 文件拷贝
	io.Copy(writer,reader)

2、案例

1、分割文件

描述:将一个大文件分割为n个小文件

参数:

  • inFilePath 大文件路径
  • outFilePath 小文件存放目录
  • splitLineNum 行号数组,如[2,4,6],会分别在大文件的第2、4、6行切割,最后生成4个小文件
func Split(inFilePath string,outFilePath string,splitLineNum []int)  {
	file, err := os.OpenFile(inFilePath, os.O_RDONLY, os.ModePerm)
	suffix := path.Ext(inFilePath)
	defer file.Close()
	if nil != err{
		fmt.Println("文件打开失败!")
		return
	}
	reader := bufio.NewReader(file)

	// 初始化变量
	cnt := 0
	index := 0
	num := splitLineNum[index]
	splitFile,_ := os.OpenFile(outFilePath+"/split_"+fmt.Sprintf("%d",index)+suffix, os.O_APPEND|os.O_CREATE, os.ModePerm)
	writer := bufio.NewWriter(splitFile)

	defer  splitFile.Close()
	for {
		line, err := reader.ReadString('\n')
		if io.EOF == err {
			break
		}
		// 判断是够读到了分割线
		if cnt == num {
			// 一定要在文件close之前flush
			writer.Flush()
			splitFile.Close()
			// 换下一个文件
			index++
			if index < len(splitLineNum) {
				num = splitLineNum[index]
			}
			splitFile,_ = os.OpenFile(outFilePath+"/split_"+fmt.Sprintf("%d",index)+suffix, os.O_APPEND|os.O_CREATE, os.ModePerm)
			writer = bufio.NewWriter(splitFile)
		}
		writer.WriteString(line)
		cnt++
	}
	writer.Flush()

}

2、计算子文件大小

描述:输入一个路径,输出该路径下所有子文件的大小,默认按文件大小从大到小排序

参数:

  • inFilePath 输入路径
  • unit 单位
func calculateChildFileSize(inFilePath string) int64{
	stat, _ := os.Stat(inFilePath)
	file, _ := os.OpenFile(inFilePath, os.O_RDONLY, os.ModePerm)
	defer file.Close()
	if stat.IsDir() {
		childs, _ := file.Readdir(-1)
		var total int64
		for _,item := range childs{
			total += calculateChildFileSize(inFilePath + "/" + item.Name())
		}
		return total
	}else{
		return stat.Size()
	}
}
func ChildFileSize(inFilePath string,unit string){
	file, _ := os.OpenFile(inFilePath, os.O_RDONLY, os.ModePerm)
	defer file.Close()
	childs, _ := file.Readdir(-1)
	var fileSizeMap map[int64]string = make(map[int64]string,10)
	for _,item := range childs{
		fileSizeMap[calculateChildFileSize(inFilePath+"/"+item.Name())] = item.Name()
	}
	var keys []int
	for k,_ := range fileSizeMap {
		keys = append(keys,int(k))
	}
	sort.Ints(keys)
	for i:=len(keys)-1;i>=0;i--{
		if unit == "KB" {
			fmt.Println(fileSizeMap[int64(keys[i])],"\t",keys[i]/1024,"KB")
		}else if unit == "MB" {
			fmt.Println(fileSizeMap[int64(keys[i])],"\t",keys[i]/(1024*1024),"MB")
		}else{
			fmt.Println(fileSizeMap[int64(keys[i])],"\t",keys[i],"B")
		}
	}
}

3、文件同步

描述:将文件从一个地方同步到另一个地方,默认以覆盖方式同步,但目的地有而源头没有的文件不会被删除

参数:

  • dst:目的地路径
  • src:源文件或源文件夹
func Sync(dst string,src string) {
	// 判断是否是文件夹,假设src是都存在的
	stat, _ := os.Stat(src)
	if !stat.IsDir() {
		f1, _ := os.OpenFile(src, os.O_RDONLY, os.ModePerm)
		if _, err := os.Stat(dst+"/"+path.Base(src));nil == err || os.IsExist(err){
			// 如果存在,则删除之前的文件
			os.Remove(dst+"/"+path.Base(src))
		}
		f2, _ := os.OpenFile(dst+"/"+path.Base(src), os.O_RDWR|os.O_CREATE, os.ModePerm)
		defer f1.Close()
		defer f2.Close()
		io.Copy(f2,f1)
	}else {
		if _, err := os.Stat(dst+"/"+path.Base(src));nil != err && os.IsNotExist(err){
			// 如果不存在,则创建文件夹
			os.Mkdir(dst+"/"+path.Base(src),os.ModePerm)
		}
		f, _ := os.OpenFile(src, os.O_RDONLY, os.ModePerm)
		childs, _ := f.Readdir(-1)
		for _,item := range childs{
			Sync(dst+"/"+path.Base(src),src+"/"+item.Name())
		}
	}
}

15、命令行参数

可以通过os.Args获取所有命令行参数。

更方便的是,可以指定参数名(如-name),在指定参数名的情况下,通过如下方式获取参数。

var name string
flag.Stringvar(&name,"name","默认值","用户名")
flag.Parse()

16、序列化与反序列化

type Student struct {
	Name string `json:"name"`
	Age int `json:"age"`
	Score int `json:"score"`
}
// 序列化
	stu := Student{"张三",23,98}
	jsonStr, _ := json.Marshal(stu)
	fmt.Println(string(jsonStr))
// 反序列化
	var stu2 Student
	_ = json.Unmarshal([]byte(jsonStr), &stu2)
	fmt.Println(stu2)

17、goroutine与channel

1、goroutine概述

go可以轻轻松松起上万个协程,原因是在底层做了优化,可以理解为更轻量级的线程

go协程的特点:

  • 有独立栈空间
  • 共享程序堆空间
  • 调度由用户控制

如果主线程退出了,则协程即使还没有执行完毕,也会退出。

主线程是一个物理线程,直接作用在CPU上,是重量级的,非常耗CPU资源。

协程是从主线程开启的,是轻量级的线程,是逻辑态,对资源消耗相对小。

其它编程语言是基于线程的,开启过多的线程资源耗费大,这一点go的并发优势就很大。

2、goroutine并发模型

MPG模式:

  • M:操作系统的主线程(是物理线程)
  • P:协程执行需要的上下文
  • G:协程

一个程序可以有多个M,当多个M在不同的CPU上运行就是并行。

每个M对应一个P、一个正在运行的G和一个协程等待队列。

如何开启一个协程?

go test()

3、并发安全

当有并发安全问题时,编译带-race参数,然后在执行exe文件,结果是正确的。

如何保证线程安全:

  • var lock sync.Mutex
    lock.Lock()
    lock.Unlock()
    
  • channel(不同goroutine之间进行通讯)

4、channel概述

channel本质就是一个队列。

多goroutine访问channel时,不需要加锁,channel本身是线程安全的。

channel是有类型的,string channel只能存放string类型。

channel的基本使用:

	// 声明channel
	var intChan chan int
	intChan = make(chan int,10)
	// 写数据,写多了就报错
	intChan<-10
	intChan<-20
	intChan<-30
	intChan<-40
	// 这个容量不会动态增长
	fmt.Println(len(intChan),cap(intChan))
	// 读数据,读多了就报错
	ele := <-intChan
	fmt.Println(ele)
	// 关闭管道,关闭之后只能读不能写
	close(intChan)
	// channel可以遍历,但如果channel没有关闭,则会报错,否则正常遍历
	for item:=range intChan{
		fmt.Println(item)
	}

注意事项:

  • channel可以声明为只读或只写。
  • 使用select可以解决从管道读数据的阻塞问题。
  • 在goroutine中使用recover,可以避免goroutine因panic而退出。

18、反射

通过反射(reflect.TypeOf函数)可以获取到变量的类型(reflect.Type接口)。

通过反射(reflect.ValueOf函数)可以获取到变量的值(reflect.Value结构体)。

19、网络编程

func server() {
	server, err := net.Listen("tcp", "0.0.0.0:8888")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer server.Close()
	fmt.Println("服务端启动成功!")
	for {
		client, _ := server.Accept()
		fmt.Println("客户端连接成功!")
		var data []byte = make([]byte,128)
		client.Read(data)
		fmt.Println("服务端接收到消息:",string(data))
	}
}
func client() {
	client, _ := net.Dial("tcp", "localhost:8888")
	defer client.Close()
	data := []byte("你好,golang!")
	client.Write(data)
}

func main() {
	go server()
	time.Sleep(1*time.Second)
	client()
}

标签:教程,string,int,fmt,速成,golang,var,Println,os
来源: https://blog.csdn.net/xl_1803/article/details/122384491

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有