golang基础
关于协程,下面说法正确是()
A. 协程和线程都可以实现程序的并发执行
B. 线程比协程更轻量级
C. 协程不存在死锁问题
D. 通过channel来进行协程间的通信
参考答案:AD
struct结构体能不能比较?
- 结构体不可以比较,但是同一类型的结构体的值可以比较是否相等(不可以比较大小);
结构体所有字段的值都相等,两个结构体才相等;
比较的两个结构体必须是相同类型才可以,也就是说他们字段的顺序、名称、类型、标签都相同才可以 - 因为是强类型语言,所以不同类型的结构不能作比较,但是同一类型的实例值是可以比较的,实例不可以比较,因为是指针类型
defer
顺序:先注册后执行,后注册先执行(类似栈、LIFO后进先出法)
触发时机:包含defer的函数返回时
包含return的函数执行到末尾时
所在的goroutine发生panic时
更多相关defer的知识可以参考https://www.jianshu.com/p/79c029c0bd58
select
用法:select就是用来监听和channel有关的IO操作,当IO操作发生时,触发相应`的动作。类似linux中的select,io多路复用机制。可以用作协程的退出
//select基本用法
select {
case <- chan1:
// 如果chan1成功读到数据,则进行该case处理语句
case chan2 <- 1:
// 如果成功向chan2写入数据,则进行该case处理语句
default:
// 如果上面都没有成功,则进入default处理流程
}
注意事项:
- 如果有一个或多个IO操作可以完成,则GO运行时系统会随机的选择一个执行,否则的话,如果有default分支,则执行default分支语句,如果连default都没有,则select语句会一直阻塞,直到至少有一个IO操作可以进行
- 所有channel表达式都会被求值、所有被发送的表达式都会被求值。求值顺序:自上而下、从左到右。
- break关键字结束select
waitgroup和context
waitgroup和context都可以用来控制并发,waitgroup类似linux中的waitpid,使用如下:
func main() {
//创建一个新的channel,输入值限制类型为int
nochan := make(chan int)
//指向waitgroup结构体
waiter := &sync.WaitGroup{}
//添加两个队列
waiter.Add(2)
//从channel里接收数据
go func(ch chan int, wt *sync.WaitGroup) {
data := <-ch
fmt.Println("receive data ", data)
wt.Done()
}(nochan, waiter)
//发送数据到channel
go func(ch chan int, wt *sync.WaitGroup) {
ch <- 5
fmt.Println("send data ", 5)
wt.Done()
}(nochan, waiter)
//先输出1,
fmt.Println(1)
//等待协程执行完
waiter.Wait()
//再输出2
fmt.Println(2)
}
udi@udi:~/Udi_work_space/Udi_ws_go/ZipTest_backend$ go run main.go
1
receive data 5
send data 5
2
context
- context可以用来跟踪goroutine,比如有一个网络请求Request,每个Request都需要开启一个goroutine做一些事情,这些goroutine又可能会开启其他goroutine。这样的话,我们就可以通过Context,来跟踪这些goroutine,并且通过Context来控制他们的目的,这就是Go语言为我们提供的Context,中文可以称之为“上下文”。
- 另外一个实际例子是,在Go服务器程序中,每个请求都会有一个goroutine去处理。然而,处理程序往往还需要创建额外的goroutine去访问后端资源,比如数据库、RPC服务等。由于这些goroutine都是在处理同一个请求,所以它们往往需要访问一些共享的资源,比如用户身份信息、认证token、请求截止时间等。而且如果请求超时或者被取消后,所有的goroutine都应该马上退出并且释放相关的资源。这种情况也需要用Context来为我们取消掉所有goroutine
生产者消费者模型实现
用channel实现生产者消费者模型
//生产者模型,往channel里输入数据
func produce(ch chan<- int) {
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("Send:", i)
}
}
//消费者模型,从channel中取出数据
func consumer(ch <-chan int) {
for i := 0; i < 10; i++ {
v := <-ch
fmt.Println("Receive:", v)
}
}
ch = make(chan int) //无缓冲
ch = make(chan int) //有缓冲
用互斥锁和条件变量实现的生产者和消费者模型:
lock := new(sync.Mutex)
cond := sync.NewCond(lock)
var count int
func producer() {
for i:=0;i<10;i++ {
lock.Lock()
count++
cond.Signal()
lock.Unlock()
}
}
func consumer(){
for i:=0;i<10;i++{
lock.Lock()
while(count == 0){
cond.Wait()
}
count--
lock.Unlock()
}
}
new和make的区别
new的作用是初始化一个指向类型的指针(T),使用new函数来分配空间。传递给new函数的是一个类型,不是一个道,不是一个值。返回值是指向这个新分配的零值的指针。
make的作用是slice,map或chan初始化并返回引用(T)。
make(T,args)函数的目的与new(T)不同,它仅仅用于创建Slice,Map和Channel,并且返回类型是T(不是T)的一个初始化的(不是零值)的实例
go触发异常的场景
空指针解析,数组越界访问,除数为0,调用panic函数。
异常处理机制panic defer recover
panicing:F函数出现panic后,终止当前函数,并调用其defer函数,然后在终止调用F函数的函数并调用其defer函数,直至goroutine退出。
panic的处理:在defer函数中调用recover获得错误值,再处理错误。
函数变长参数使用
类型转换
type类型不能直接转换,只能强制转换,比如:
type Myint int
var a int
var b Myint
a = b // 错误
a = int(b) //正确
append,delete用法
append用来添加map中的元素
delete用来删除map中的元素
接口方法集调用规则
- 类型T的可调用方法集包含接受者为T或者T的所有方法集
- 类型T的可调用方法集包含接受者为T的所有方法
- 类型T的可调用方法集不包含接受者为*T的方法