1. go的锁如何实现,用了什么cpu指令

第一种:mutex

var num int
var mtx sync.Mutex
var wg sync.WaitGroup

func add(){
mtx.Lock()

defer mtx.Unlock()
defer wg.Done()
num+=1
}

func main(){
for i:= 0; i < 100; i++{
wg.Add(1)
go add()
}
wg.Wait()
}

第二种 使用chan实现

var num int
func add(h chan int, wg *sync.WaitGroup){
defer wg.Done()
h <- 1
num += 1
<-h
}
func main(){
ch := make(chan int,1)
wg := &synv.WaitGroup{}
for i:=0;i < 100; i++{
wg.Add(1)
go add(ch,wg)
}

wg.Wait()
fmt.Println("num",num)
}

2. waitGroup简介

add 增加技术
done:减少一个计数,等价于 add(-1)
wait:卡住,直到计数等于0每个goroutine启动前,增加一个计数,每一个goroutine结束前,减少计数。
func wgTest(wg *sync.WaitGroup){
//计数减一
defer wg.Done()
}func main(){
wg := sync.WaitGroup{}
for i:= 0; i < 10; i++{
wg.Add(1)
go wgTest(&wg)
}
//计数为0时,结束
wg.Wait()
}

3.channel

channel用于协程之间的通信,让协程之间可以相互交换数据。像管道一样,一个goroutine_A像channel_A中放数据,另一个goroutine_B从channel_B中取数据。

channel是指针类型的数据类型,通过make来分配内存
ch := make(chan int)

<-ch  //从ch中读取一个值
val := <-ch //从ch中读取一个值并保存到val变量中
val,ok = <-ch //从ch中读取一个值,判断是否读取成功,如果读取成功,则保存到val变量中

channel的3种操作

  1. send:表示sender端的goroutine向channel中投放数据
  2. receive:表示receiver端的goroutine从channel中读取数据
  3. close:表示关闭channel,关闭channel后,send操作将导致panic;recv操作将返回对应类型的0值以及一个状态码false

channel的两种类型
1.阻塞、同步模式:
sender端向channel中发送一个数据,然后阻塞,知道receiver端将此数据接收
receiver端一直阻塞,知道sender端向channel发送一个数据

2.非阻塞,异步模式:
sender端向channel中send多个数据,容量满之前不会阻塞
receiver端按照队列的方式从buffered channel中按序receive其中数据

3.两个属性
capcity: 表示最多可以缓冲多少个数据make(chan TYPE,CAP)
length:表示当前已经缓冲了多少数据

unbuffered channel可以认为是容量是0的buffered channel,所以每发送一个数据就被阻塞。注意不是容量为1的buffered channel,容量为1的channel,是在channel中已有一个数据,发送第二个数据的时候才被阻塞。

  1. 死锁
    当channel的某一端(sender/receiver)期待另一端的(receiver/sender)操作,另一端正好在期待本端的操作时,也就是说两端都因为对方而使得自己当前处于阻塞状态,这时将会出现死锁问题。
    通俗说,只要所有goroutine都被阻塞,就会出现死锁。
func main(){
goo(32)
}
func go(s int){
counter := make(chan int)
counter <- s
fmt.Println(<-counter)
}

channel的send和recv操作都是在同一个goroutine中进行的,send会阻塞main goroutine,使得recv操作无法被执行,所以go会报错

修复此问题,只需要将send操作放在另一个goroutine中执行即可:

func main(){
goo(32)
}
func go(s int){
counter := make(chan int)
go func(){
counter <- s
}
fmt.Println(<-counter)
}
或者将counter设置为一个容量为1的buffered channel
counter := make(chan int,1)
  1. unbuffered channel同步通信示例
// wg用于等待程序执行完成
var wg sync.WaitGroup

func main() {
count := make(chan int)

// 增加两个待等待的goroutines
wg.Add(2)
fmt.Println("Start Goroutines")

// 激活一个goroutine,label:"Goroutine-1"
go printCounts("Goroutine-1", count)
// 激活另一个goroutine,label:"Goroutine-2"
go printCounts("Goroutine-2", count)

fmt.Println("Communication of channel begins")
// 向channel中发送初始数据
count <- 1

// 等待goroutines都执行完成
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating the Program")
}
func printCounts(label string, count chan int) {
// goroutine执行完成时,wg的计数器减1
defer wg.Done()
for {
// 从channel中接收数据
// 如果无数据可recv,则goroutine阻塞在此
val, ok := <-count
if !ok {
fmt.Println("Channel was closed:",label)
return
}
fmt.Printf("Count: %d received from %s \n", val, label)
if val == 10 {
fmt.Printf("Channel Closed from %s \n", label)
// Close the channel
close(count)
return
}
// 输出接收到的数据后,加1,并重新将其send到channel中
val++
count <- val
}
}

上面的程序,激活了两个goroutine,激活这两个goroutine后,向channel中发送一个初始数据值1,然后main goroutine将因为wg.Wait等待两个goroutine都执行完而被阻塞。
在看这两个goroutine,执行完全一样的代码。都接收count这个channel的数据,但可能goroutine1先接收到channel中的初始值1,也可能是goroutine2先接收到初始值1.接收到数据后输出值,并在输出后对数据加1,然后将加1后的数据再次send到channel,每次send都会将自己这个goroutine阻塞,此时,另一个goroutine因为等待recv而执行。当加1后发送给channel的数据为10之后,某goroutine将关闭count channel,该goroutine将退出。wg的计数器-1,另一个goroutine因为等待recv而阻塞的状态将因为channel的关闭而失败,ok状态码将让该goroutine退出,于是wg的计数器减为0,main goroutine因为go.Wait()而继续执行后面的代码。

5.go的runtime如何实现

goland的程序都是在runtime的基础上运行的,除了与底层直接交互的syscall
runtime.h中有很多数据结构和接口:
1.G,代表的是goroutine,开启一个goroutine实际就是实例化一个G
2.M,代表Machine. M中存放go程序和机器CPU交互的数据结构
一个go程序都附带一个runtime,负责与底层操作系统做交互
go程序的启动流程:
1.调用osinit,操作系统级别的初始化
2.调用runtime.schedinit
3.调用runtime.mstart启动M
4.调用runtime.main

5.go的数据库连接池实现

6.ctx包有什么用?

1.每一个长请求都应该有个超时限制
2.需要在调用中传递这个超时,通过channel来通知请求结束了。
3.优雅解决goroutine启动后不可控问题。

func main() {
ctx, cancel := context.WithCancel(context.Background())
go watch(ctx,"【监控1】")
go watch(ctx,"【监控2】")
go watch(ctx,"【监控3】")

time.Sleep(10 * time.Second)
fmt.Println("可以了,通知监控停止")
cancel()
//为了检测监控过是否停止,如果没有监控输出,就表示停止了
time.Sleep(5 * time.Second)
}

func watch(ctx context.Context, name string) {
for {
select {
case <-ctx.Done():
fmt.Println(name,"监控退出,停止了...")
return
default:
fmt.Println(name,"goroutine监控中...")
time.Sleep(2 * time.Second)
}
}
}

7. go什么情况下会发生内存泄漏?

ctx没有cancel的时候

8. 怎么实现协程完美退出?