Go教程

go语言学习记录

本文主要是介绍go语言学习记录,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
简介

go文件头部用package声明所属包名称

package main

func main() {
    println("hello")
}

以.go作为文件扩展名,语句结束分号会被默认省略,支持“ // ”进行注释,入口函数main没有参数,且必须放在main包中。

通过import 导入标准库和第三方包。
未使用的导入需要删除,否则会报错

package main
import {
    "fmt"
}

func main() {
    fmt.Println("hello")
}

运行方式可直接运行,或编译为可执行文件。

go run main.go     直接运行
go build main.go   编译

备注

关于包

1.同一个包下面的文件属于同一个工程文件
2.同一个包下面的package 的名字要一致
3.package的名字建议都是文件夹的名字 比如:package hello
4.main函数所在的包名必须是package main

导入包

1:在包的前面加个点(.)例如: .“fmt” 指使用的时候不需要写包的名字,直接调用函数就可以: Println(“hello world”)
2.在包的前面加上别名,例如: a “fmt” 给包起一个别名 调用的时候: a.Println(“hello”)
3. 在包的前面加上下划线(_),例如: _ “fmt” 只调用该包的init函数。

常用命令

go run -n test.go // 查看go的编译过程,生成文件
go run ./test.go // 直接编译test.go文件
go build // -n 直接编译文件生成.exe可执行文件,把可执行文件移动
go install // build以后直接install,把exe文件移动到bin目录下,
//库文件生成.a文件->pkg目录,
// main文件生成.exe文件->bin目录下
go get +包名获取远程包 //从远程获取扩展包,安装到pkg文件夹
go list // 查看当前安装的所有包

命名规范

1.当命名(常量,变量,类型,函数名,结构体等)以大写字母开头,
2.大写字母说明是公有的public ,可以被外部访问到的
3.当命名为小写字母开头的时候:对包外部是不可见的,但是在整个包内部是可见可用的,private
4.采用驼峰法,首字母控制大写小写,注释说明,struct 声明和初始化采用多行
5.接口命名:type 接口名 interface{} ,一般以 er为后缀
6.常量名称:全部大写+下划线等,比如:USER_NAME

注意事项

1.花括号换行,会报错
2.不需要分号结尾

go 的数据类型 : Basic Types(基础类型)、Aggregate Types (复合类型)、Reference Types (引用类型)、Interface Types (接口类型)

基础类型包括:数字、字符串和布尔型。
复合数据类型包括:数组、结构体是通过组合简单类型。
引用类型包括:指针、slice、map、 channel、接口和函数类型。

值转移和引用转移

切片和字典都是引用类型。当地被赋值为一个新变量的时候,它们指向同一个内部数据结构。因此,改变其中一个变量,就会影响到另一变量。

图片描述

变量

支持使用var定义变量,类型推断。基础数据类型划分清晰,编译器确保变量总是被初始化为零值。
图片描述

常量

1.常量定义了不使用不会报错
2.不需要使用 :=声明
3.一组常量如果默认没有初始值,那么值和上一个常量一致
4.常量数值类型只能是:布尔,数值(整数,浮点,复数),字符串类型

图片描述

枚举类型:iota

go语言中的枚举是从0开始
iota常量自动生成器,每隔一行,自动累加1
iota给常量赋值用
iota遇到const,重置为0
iota如果是同一行,值都一样

图片描述

结构体(struct)

结构体可以匿名嵌入其他类型。

图片描述

方法

可以为当前包内的任意类型定义方法。

还可以直接调用匿名字段的方法,这种方式类似于继承。

图片描述

接口

只要包含了接口中所需的全部方法,即表示实现了该接口。

另有空接口类型interface{},可接收任意类型对象。

并发

go 语言中整个运行时完全是并发设计的,一切都在以goroutine 方式运行。这是一种比普通协程或线程更加高效的并发设计。

图片描述

通过使用channel与goroutine搭配,实现用通信代替共享内存的CSP模型。

图片描述

CSP通信模型(Communicating Sequential Process)
CSP并发模型

CSP模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。

Golang CSP

Golang 就是借用CSP模型的一些概念为之实现并发进行理论支持,其实从实际上出发,go语言并没有,完全实现了CSP模型的所有理论,仅仅是借用了 process和channel这两个概念。process是在go语言上的表现就是 goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。

Channel

Golang中使用 CSP中 channel 这个概念。channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中 channel 是同步的一个消息被发送到 channel 中,最终是一定要被另外的实体消费掉的,在实现原理上其实是一个阻塞的消息队列。

Goroutine

Goroutine 是实际并发执行的实体,它底层是使用协程(coroutine)实现并发,coroutine是一种运行在用户态的用户线程,类似于 greenthread,go底层选择使用coroutine的出发点是因为,它具有以下特点:
用户空间 避免了内核态和用户态的切换导致的成本可以由语言和框架层进行调度更小的栈空间允许创建大量的实例可以看到第二条 用户空间线程的调度不是由操作系统来完成的,像在java 1.3中使用的greenthread的是由JVM统一调度的(后java已经改为内核线程),还有在ruby中的fiber(半协程) 是需要在重新中自己进行调度的,而goroutine是在golang层面提供了调度器,并且对网络IO库进行了封装,屏蔽了复杂的细节,对外提供统一的语法关键字支持,简化了并发程序编写的成本。

Goroutine 调度器

上节已经说了,golang使用goroutine做为最小的执行单位,但是这个执行单位还是在用户空间,实际上最后被处理器执行的还是内核中的线程,用户线程和内核线程的调度方法有:
N:1 多个用户线程对应一个内核线程
1:1 一个用户线程对应一个内核线程
图片描述
M:N 用户线程和内核线程是多对多的对应关系
golang 通过为goroutine提供语言层面的调度器,来实现了高效率的M:N线程对应关系
调度示意
图中
M:是内核线程P : 是调度协调,用于协调M和G的执行,内核线程只有拿到了 P才能对goroutine继续调度执行,一般都是通过限定P的个数来控制golang的并发度G : 是待执行的goroutine,包含这个goroutine的栈空间Gn : 灰色背景的Gn 是已经挂起的goroutine,它们被添加到了执行队列中,然后需要等待网络IO的goroutine,当P通过 epoll查询到特定的fd的时候,会重新调度起对应的,正在挂起的goroutine。Golang为了调度的公平性,在调度器加入了steal working 算法 ,在一个P自己的执行队列,处理完之后,它会先到全局的执行队列中偷G进行处理,如果没有的话,再会到其他P的执行队列中抢G来进行处理。

总结

Golang实现了 CSP 并发模型做为并发基础,底层使用goroutine做为并发实体,goroutine非常轻量级可以创建几十万个实体。实体间通过 channel 继续匿名消息传递使之解耦,在语言层面实现了自动调度,这样屏蔽了很多内部细节,对外提供简单的语法关键字,大大简化了并发编程的思维转换和管理线程的复杂性。

golang

支持跨平台编译

windows下编译Mac平台64位可执行程序:

SET CGO_ENABLED=0;  // 禁用CGO
SET  GOOS=darwin      // 目标平台是Linux
SET  GOARCH=amd64   // 目标处理器架构是amd64
go build
print printf println区别

print输出不会换行
println会换行输出
多参数输入时,print输出无空格
多参数输入时,println输出有空格

printf是格式化输出
printf(“a=%d", a)
printf输出一个变量的类型

占位符

普通占位符

%v 相应值的默认格式。 Printf("%v", people) {zhangsan},
%+v 打印结构体时,会添加字段名 Printf("%+v", people) {Name:zhangsan}
%#v 相应值的Go语法表示 Printf("#v", people) main.Human{Name:“zhangsan”}
%T 相应值的类型的Go语法表示 Printf("%T", people) main.Human
%% 字面上的百分号,并非值的占位符 Printf("%%") %

布尔占位符

%t true 或 false。 Printf("%t", true) true

整数占位符

%b 二进制表示 Printf("%b", 5) 101
%c 相应Unicode码点所表示的字符 Printf("%c", 0x4E2D) 中
%d 十进制表示 Printf("%d", 0x12) 18
%o 八进制表示 Printf("%d", 10) 12
%q 单引号围绕的字符字面值,由Go语法安全地转义 Printf("%q", 0x4E2D) ‘中’
%x 十六进制表示,字母形式为小写 a-f Printf("%x", 13) d
%X 十六进制表示,字母形式为大写 A-F Printf("%x", 13) D
%U Unicode格式:U+1234,等同于 “U+%04X” Printf("%U", 0x4E2D) U+4E2D

浮点数和复数占位符

%b 无小数部分的,指数为二的幂的科学计数法,
与 strconv.FormatFloat 的 ‘b’ 转换格式一致。例如 -123456p-78
%e 科学计数法,例如 -1234.456e+78 Printf("%e", 10.2) 1.020000e+01
%E 科学计数法,例如 -1234.456E+78 Printf("%E", 10.2) 1.020000E+01
%f 有小数点而无指数,例如 123.456 Printf("%f", 10.2) 10.200000
%g 根据情况选择 %e 或 %f 以产生更紧凑的(无末尾的0)输出 Printf("%g", 10.20) 10.2
%G 根据情况选择 %E 或 %f 以产生更紧凑的(无末尾的0)输出 Printf("%G", 10.20+2i) (10.2+2i)

字符串和字节切片

%s 输出字符串表示(string类型或[]byte) Printf("%s", []byte(“Go语言”)) Go语言
%q 双引号围绕的字符串,由Go语法安全地转义 Printf("%q", “Go语言”) “Go语言”
%x 十六进制,小写字母,每字节两个字符 Printf("%x", “golang”) 676f6c616e67
%X 十六进制,大写字母,每字节两个字符 Printf("%X", “golang”) 676F6C616E67

指针

%p 十六进制表示,前缀 0x Printf("%p", &people) 0x4f57f0

别的须知

golang没有 ‘%u’ 点位符,若整数为无符号类型,默认就会被打印成无符号的。
宽度与精度的控制格式以Unicode码点为单位。宽度为该数值占用区域的最小宽度;精度为小数点之后的位数。
操作数的类型为int时,宽度与精度都可用字符 ‘*’ 表示。
对于 %g/%G 而言,精度为所有数字的总数,例如:123.45,%.4g 会打印123.5,(而 %6.2f 会打印123.45)
对于 %f 而言,精度为小数位数,例如:123.452435,%.4g 会打印123.4524。
%e 和 %f 的默认精度为6
对大多数的数值类型而言,宽度为输出的最小字符数,如果必要的话会为已格式化的形式填充空格。
而以字符串类型,精度为输出的最大字符数,如果必要的话会直接截断。

变量

go中变量声明了必须要使用。
变量声明的方式:1、直接声明,2、类型推导
直接声明:var a int = 10
类型推导:a := 10 // 此处声明的变量只能用于局部变量。
一次声明多个变量:var (
username string
age int
sex string
)
username = "张三”
age = 20
sex = “男”
一次声明多个变量:a,b,c := 12,13,“C”

匿名变量:var usename,_ = 13, "C"
匿名变量不存在重复声明周期。

同一作用域下的变量不能重复声明。

变量名由字母、数字、下划线组成,首个字符不能为数字,go语言中的关键字和保留字都不能用。

go语言是强类型语言,静态语言

go语言中严格区分大小写。

常量

const pi = 3.14
常量定义的时候一定要给他赋值。常量的值是不可以改变的。
定义常量建议都用大写。
同时声明多个常量:const(
a =“a”
b = 12

const(
n1 = 100
n2
n3

iota是go语言中的常量计数器,只能在常量的表达式中使用。每次const都会重置iota为0。
const (
n1 = iota //0
n2 //1
n3 //2
)
const (
n1 = iota //0
n2 = 100 //100
n3 = iota // 2
n4 // 3
)
const (
n1, n2 = iota +1, iota +2 // 1,2
n3, n4 // 2,3
n5, n6 // 3,4
)

go语言代码风格

每行不用分号
强制代码风格
if后面的括号必须紧挨变量
使用驼峰命名规则。

go中数据类型

基本类型

整形:
uintptr类型,无符号整型,用于存放一个指针。
int类型,都是有符号: int8,int16,int32,int64
uint类型,都是无符号:uint8,uint16,uint32,uint64

unsafe.Sizeof() // 可以打印出类型占据的存储空间大小。

不同类型可以进行强制类型转换,但是不会进行隐式转换。高位向低位转换会截取并舍去多余位数。

%T输出为int,表示是int64类型

浮点型:
float32:3.4e38
float64:1.8e308

go语言中默认float是float64类型。

go语言中float存在精度丢失问题。在定长条件下,二进制小数和十进制小数互转可能有精度丢失问题。
var f4 float64 = 1129.6
fmt.println( f4 * 100 ) // 112960.9999999

m1 := 8.2
m2 := 3.8
fmt.println( m1 - m2 ) // 4.39999999
可以用过第三方包 decimal来解决。

string类型变量的默认值为空
int类型变量默认值为0
float型变量的默认值为0
布尔类型变量的默认值为false

go语言中不允许将整型强制转换为布尔型
go语言中不允许将布尔型隐式转换为其它类型参与运算

字符串转义符

\r 回车符
\n 换行符
\t 制表符
’ 单引号
" 双引号
\ 反斜杠

字符串的常用方法

len() , +或fmt.sprintf()(用于拼接字符串) , strings.Split() , strings.Join() , strings.contains()(判断字符串中是否包含另一个字符串) , strings.HasPrefix | HasSuffix (判断前缀或后缀是否包含某字符串), strings.Index() , strings.LastIndex()(返回查找到的位置) 。
通过 来输出多行字符串。

golang中一个汉字(utf-8)占用3个字节,一个字母占用一个字节。

golang中字符串用结构体+指针实现,所以unsafe.Sizeof没法打印出它的存储空间。
用len来看字符串的存储空间

golang中字符

uint8类型或者说是byte类型,代表ASCII码的一个字符,
rune类型,代表一个utf-8字符,通过for range出来的是一个rune类型。

修改字符串的内容

先把字符串转为[]byte或者[]rune,再转成字符串。

go中的运算符

a%b ==> a - ( a / b ) * b
在golang中,++和–只能独立使用,不算作运算符。

位运算符:&按位与,| 按位或 ,^按位异或, a << b a左移b位。 >> 右移

在golang中switch case里可以不写break。不会穿透,写了fallthrough就会穿透。如果case中写表达式则switch后不写变量。

break只能跳出当前循环

花括号不连着不会有影响。

if else 不连着结束花括号会报错

在多重循环中可以用label跳出循环。

label1:
    for i:=0 ; i<9; i++ {
        for j:=0 ; j<3 ; j++ {
            if j == 3 {
                break : label1
            }
        }
    }

continue结束当前循环,开始下一次循环。

goto 通过标签进行代码间的无条件跳转。
goto结合label进行直接跳转到指定位置进行执行。

基本数据类型和数组都是值类型

改变变量副本的值时不会改变变量本身

切片都是引用类型

改变变量副本的值时会改变变量本身

多维数组
var arr = [3][2]string{
{"ss","kk"},
{"ss","kk"},
{"ss","kk"},
}
切片
a := []{} // 切片
b := [2]int{ 33, 44 }
c := b[ : ] //切片,将b数组中的所有值赋值给c。

切片只能用append进行添加。切片没有赋值只进行声明,则为nil。

切片的长度是它所包含的元素个数。
容量是从它第一个元素到其底层数组末尾的个数。

切片扩容是在长度等于容量之后,下一次容量扩容为上一次的一倍。
切片只能通过append添加元素,简单理解为可变长数组。
a := make([]int, 4,3)
切片中没有删除的方法,可以通过append(ar[:2],ar[3:]…)来删除掉需要删除的数。
通过[]rune将字符串进行转换,然后修改,再用string方法变回一个字符串,从而修改了字符串内容。

排序
  1. 选择排序
    选择排序的思路是确定新数组每一位的数字
  2. 冒泡排序
    冒泡排序的思路是每个数字在数组中哪个位置。

sort包可以进行升序排列,sort.Ints(),sort.Float64s(),sort.Strings()
如果降序排,
sort.Sort(sort.Reverse(sort.IntSlice())),
sort.Sort(sort.Reverse(sort.Float64Slice())),
sort.Sort(sort.Reverse(sort.StringSlice()))

map
  • map的curd

    var userinfo = make(map[string]string)
    userinfo[“age”] = “20” // 增加和修改对应key的value
    v,ok := userinfo[“age”] // ok是判断key是否存在,v是value的值。
    delete(userinfo,“age”) // 删除key
    var userinfo = map[string]string{
    “username”: “男”
    }

  • map和切片可以互相嵌套存放。

type和匿名函数、defer

type用来进行类型声明,函数内部只能使用匿名函数执行,如果不是匿名函数无法在函数内部执行。

n–和n++是单独的语句不能进行赋值。

defer进行一个延迟方法的定义,则必须要执行。在defer归属的函数即将返回时,将延迟处理语句按defer定义的逆序进行执行。先被defer的后执行,后defer的先执行。
defer后面要跟着匿名自执行函数或匿名语句

go的复合类型之切片、map、struct、数组
Struct
// 第一种方式声明
var p struct {
x, y float64
}

// 第二种方式声明
type Point struct {
x, y float64
}
var p Point

struct是值类型,new(StructType)返回一个指向零值的指针(分配的内存都被置0)。

只有当结构体的字段(和方法,即将讲解)名字的首字母大写时,它才能被包外可见。

type Point struct {
X, Y float64 // exported
name string // not exported
}

type C struct {
x float64
int
string
}
c := C{3.5, 7, "hello"}
fmt.Println(c.x, c.int, c.string)

type A struct {
ax, ay int
}
type B struct {
A
bx, by float64
}

//  这样可能会产生冲突
type A struct { a int }
type B struct { a, b int }
type C struct { A; B }
var c C

//   但是下面这样不会产生冲突
type B struct { a, b int }
type D struct { B; b float64 }
var d D

切片和数组

string.split( “” ) // 出来的是一个切片
这个和数组还不一样。
在golang中,有一个“罕见”的复合类型,叫切片,切片是基于数组的,golang和其它语言不一样,在golang中,数组是不可变的,对数组进行类型转换等操作都会导致golang隐式的申请一块内存,然后将原数组的内容复制到这块内存。

数组是不可变的,这就决定了字符串也是不可变的,因为字符串底层就是一个byte数组实现的。

切片是基于数组的,它表示一个拥有相同类型元素的可变长度的序列。

切片包含长度,容量,指针三个属性,指针指向的是数组的某块区域,容量一般是数组的长度,而长度指的是切片开始的位置到切片结束的位置的长度,而容量是指切片开始的位置到数组结束的位置

声明一个数组:arr := […]int{1,2,3,4,5}
声明一个切片:slice := []int{1,2,3,4,5}
加上省略号,表示可以不写数组长度,而是根据后面的初始化的序列来推断出数组长度,而省略了省略号,就表示声明一个切片

go语言中,数组是不可变的,把数组赋值给另外一个变量实际上是对数据的深拷贝

go语言中的数组有,byte【】,rune【】,
byte 等同于int8,常用来处理ascii字符
rune 等同于int32,常用来处理unicode或utf-8字符

切片长度的扩充

golang提供了append方法,用于向切片后面追加元素,同样,数组的两倍容量 > 如果切片的容量 > 数组的容量,这样做是为了分摊线性复杂度。如果切片的容量 < 数组的容量,则只需要将切片长度+ 1即可。

//往slice后面追加元素
func appendInt(slice []int, value int) []int{
    //需要返回的切片
    var z []int
    zlen := len(slice) + 1

    //判断是否超出slice的容量 
    if zlen <= cap(slice) {
        z = slice[:zlen]
    } else {
        //超出即申请创建一个新的数组,返回该数组的切片
        zcap := zlen
        //分摊线性时间复杂度
        //避免重复的申请空间
        if zcap < len(slice) * 2 {
            zcap = len(slice) * 2
        }
        z = make([]int, zlen, zcap)
    }
    z[len(slice)] = value
    copy(z, slice)
    return z
}

切片能不能作为map的键呢

答案是不能,slice是不能比较的,因为slice类似于引用,引用只能比较两个引用是否指向同一块内存,golang并没有提供slice比较方法,如果真的要比较slice,那么可以循环遍历slice,挨个挨个比较元素,但是这样做是不理想的,因为slice依赖于底层数组,数组元素改变,slice也会跟着改变。

实参和形参的传递

函数的实参传递,实际上就是把实参拷贝给形参,当传入一个数组的时候,也是拷贝给了形参,
当传递一个切片的时候,也是把该切片拷贝给形参,但是切片相当于是数组的引用,因此,引用的拷贝,是可以在函数内部修改的

string类型存储ascii和unicode字符

string的底层就是byte数组,现在将它转换为rune数组(在实际开发中,我们经常用这个转换来处理中文字符),这些代码做了几件事情,首先是申请一块内存,存入rune类型的数组,然后将hello 世界的值拷贝到这个数组里面来,然后返回给这个数组的引用。
对数组的类型转换,也会导致go去重新申请一块内存

new和make函数

new函数:
它只接受一个参数,这个参数是一个类型,分配好内存后,返回一个指向该类型内存地址的指针。同时请注意它同时把分配的内存置为零,也就是类型的零值。

func main() {
 var i *int
 i=new(int)
 *i=10
 fmt.Println(*i)
}

make函数:
make也是用于内存分配的,但是和new不同,它只用于chan、map以及切片的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了,但是不是置为零值。

new函数用的不多,但是make函数无可替代。

go语言中的map数据类型

Go语言中的map(映射,字典)是一种内置的数据结构,它是一个无序的key-value对的集合。
在一个map里所有的键都是唯一的,而且必须是支持==和!=操作符的类型
切片、函数以及包含切片的结构类型这些类型由于具有引用语义,不能作为映射的键
map里所有键的数据类型必须是相同的,值也必须如何,但键和值的数据类型可以不相同。
map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺利有可能不同。

 //1、创建
    var g1 map[int]string //默认值为nil
    g2 := map[int]string{}
    g3 := make(map[int]string)
    g4 := make(map[int]string, 10)
    fmt.Println(g1, g2, g3, g4, len(g4))

    //2、初始化
    var m1 map[int]string = map[int]string{1: "ck_god", 2: "god_girl"}
    m2 := map[int]string{1: "ck_god", 2: "god_girl"}
    fmt.Println(m1, m2)

    //3、结果   
    //map[] map[] map[] map[] 0
    //map[1:ck_god 2:god_girl] map[1:ck_god 2:god_girl]
    
go语言中的channel

线程通信在每个编程语言中都是重难点,在Golang中提供了语言级别的goroutine之间通信:channel

channel是进程内通信方式,每个channel只能传递一个类型的值.这个类型需要在声明channel时指定

channel在Golang中主要的两个作用是同步和通信

channel在golang中可以创建一个有缓存通道

声明channel的语法

var 名称 chan 类型
var 名称 chan <- 类型 //只写var 名称 <- chan 类型//只读
名称:=make(chan int) //无缓存channel
名称:=make(chan int,0)//无缓存channel
名称:=make(chan int,100)//有缓存channel

操作channel的语法

ch <- 值 //向ch中添加一个值<- ch //从ch中取出一个值
a:=<-ch //从ch中取出一个值并赋值给a
a,b:=<-ch//从ch中取出一个值赋值给a,如果ch已经关闭或ch中没有值,b为false

使用channel实现goroutine之间通信

package main
 
import (
   "fmt")
 
func main() {
   //用于goroutine之间传递数据
   ch := make(chan string)
   //用于控制程序执行
   ch2 := make(chan string)
   go func() {
      fmt.Println("执行第一个goroutine,等待第二个goroutine传递数据")
      content := <-ch
      fmt.Println("接收到的数据为:", content)
      ch2 <- "第一个"
   }()
   go func() {
      fmt.Println("进入到第二个,开始传递数据")
      ch <- "内容随意"
      close(ch)
      fmt.Println("发送数据完成")
      ch2 <- "第二个"
   }()
   result1 := <-ch2
   fmt.Println(result1, "执行完成")
   result2 := <-ch2
   fmt.Println(result2, "执行完成")
   fmt.Println("程序执行结束")}
go语言中的goroutine

协程又称为微线程与子例程,协程也是一种程序组件,相对子例程而言,协程更为灵活,与线程类似,共享堆,而不共享栈,协程的切换一般由程序员在代码中显示控制。它避免了上下文的切换的额外消耗,兼顾了多线程的优点,简化了高并发程序的复杂。

goroutine可以简单理解为一种go语言的协程,同时它可以运行在一个或多个线程上。goroutine支持并发,但是协程不支持并发,goroutine支持并发是go语言在语言层面支持并发。

go并发的实现原理

GO实现了俩种并发形式,一种是多线程共享内存,第二种是GO推荐的是CSP(并发模型)(以通信的方式来共享内存)。
多线程共享内存:在访问共享数据的时候,通过锁来访问,因此很多时候,衍生出一种方便操作的数据结构,叫做“线程安全的数据结构”。
CSP:通过channel和goroutine来实现的,goroutine是GO语言中并发的执行单位,channel是GO语言中各个并发结构体(goroutine)之前的通信机制。就是各个goroutine之间通信的“管道”。

main()本身也是运行了一个goroutine,是主goroutine。

GO语言的线程模型就是一种特殊的两级线程模型(GPM调度模型)。
G: goroutine,协程
M: machine,内核线程,对应CPU
P: processor,G和M的调度对象,包含LRQ

可以使用for range获取channel中内容

func main() {
   ch:=make(chan string)
   ch2:=make(chan int)
   go func() {
      for i:=97;i<97+26;i++{
         ch <- strconv.Itoa(i)
      }
      ch2<-1
   }()
 
   go func() {
      for c := range ch{
         fmt.Println("取出来的",c)
      }
   }()
   <-ch2
   fmt.Println("程序结束")}

channel是安全的.多个goroutine同时操作时,同一时间只能有一个goroutine存取数据

package main
 
import (
   "time"
   "fmt")
 
func main() {
   ch := make(chan int)
 
   for i := 1; i < 5; i++ {
      go func(j int) {
         fmt.Println(j, "开始")
         ch <- j
         fmt.Println(j, "结束")
      }(i)
   }
 
   for j := 1; j < 5; j++ {
      time.Sleep(2 * time.Second)
      <-ch
   }}
死锁

在主goroutine中向无缓存channel添加内容或在主goroutine中向channel添加内容且添加内容的个数已经大于channel缓存个数就会产生死锁

死锁:在程序中多个进程(Golang中goroutine)由于相互竞争资源而产生的阻塞(等待)状态,而这种状态一直保持下去,此时称这个线程是死锁状态
死锁情况

func main() {
   ch := make(chan int)
   ch <- 1}

非死锁情况

package main
 
import (
   "time"
   "fmt")
 
func main() {
   ch := make(chan int)
   go func() {
      ch <- 1
      fmt.Println("执行goroutine")
   }()
   time.Sleep(5e9)
   fmt.Println("程序执行结束")}
这篇关于go语言学习记录的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!