215.go面试题

 


1.统计字母出现的频率

面试官建议使用生产者消费者模型

type LetterFreq map[rune]int

func CountLetters(strs []string, concurrency int) LetterFreq {

	if len(strs) < concurrency {
		concurrency = len(strs)
	}
	ch := make(chan rune, concurrency)
	result := make(map[rune]int)

	wg := sync.WaitGroup{}
	lock := sync.Mutex{}
	for ; concurrency > 0; concurrency-- {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for c := range ch {
				lock.Lock()
				result[c]++
				lock.Unlock()
			}
		}()
	}

	go func() {
		defer close(ch)
		for _, str := range strs {
			for _, c := range str {
				ch <- c
			}
		}
	}()
	wg.Wait()
	return LetterFreq(result)
}

2.go切片的问题

不超过原切片长度之前, 切片和原切片公用一个内存地址

func test2() {
	x := []int{1, 2, 3}
	y := x[:2]        // 1.这里y和x公用一个长度slice地址
	y = append(y, 50) // 2.使用50将3替换掉了
	y = append(y, 60) // 3.这里发现y的长度是3, 加不进去了, 所有有复制一份内存给y
	y[0] = 20         // 4.如果不添加60, 会导致x, y一起变, 但是因为添加了60创建了新数组, 所以只改变有
	fmt.Printf("x=%#v\n", x)

	fmt.Printf("y=%v\n", y)
}

3.优化代码

当切片长度确定时, 可以直接分配定长, 减少扩容

func test3(n int) []Obj {

	//refs := make([]*Obj, 0) // 1. 这里的长度n已经确定可以将容量直接确定, 因为slice在容量不够的时候回*2方式进行扩容, 扩容很耗时
	//for i := 0; i < n; i++ {
	//	obj := &Obj{id: i}
	//	refs = append(refs, obj)
	//}
	
	// 解决方法
	refs := make([]Obj, n) // 2.直接把返回值, 和refs给改成这样, 那么这些obj申请分配内存的时候回连续, 加快访问效率
	for i := 0; i < n; i++ {
		refs[i].id = i
	}
	return refs
}

4.go无buffer chan可能导致的问题

var (
	wg       sync.WaitGroup
	execTime time.Duration = time.Second
)

func finishReq(timeout time.Duration) int {
	ch := make(chan int) //1. 这里因为是一个无buffer的chan, 只有当有协程读的时候才能写入, 如果没有协程读会导致死锁
	wg.Add(1)
	go func() {
		defer wg.Done()
		time.Sleep(execTime) // Simulate time spent
		ch <- 42             // 3. 而这里在经过execTime时间之后想向里面写入数据, 但是没人读导致协程卡死
	}()
	// 2. 下面的select因为timeout<execTime的原因, 走到了下面的case, select语句执行了之后不会循环, 导致没人读
	select {
	case result := <-ch:
		return result
	case <-time.After(timeout): // 如果time.After(timeout)超过timeout时间会返回一个chan, 将当前时间写进去
		log.Print("Timeout")
		return -1
	}
}

func test4() {
	timeout := 50 * time.Millisecond
	log.Printf("Result: %d", finishReq(timeout))
	wg.Wait() // 4. 这里还在等待协程执行完成, 但是在第3步已经卡死了, 程序不会结束, 直接导致死锁
}

5.go select随机选取问题

func test5() {
	// 问什么? 执行时间超过两秒
	stopCh := make(chan struct{})
	ticker := time.NewTicker(100 * time.Millisecond) // 1. 创建了一个时间周期的结构, 其中有一个C是一个Chan, 按照传入的时间周期去向C中写入数据
	go func() {
		time.Sleep(2 * time.Second) // 2. 等待2秒关闭, 读取空管道可以返回一个未初始化的结构体, 如果int返回0, struct{}返回{}
		close(stopCh)
	}()
	for {
		// 4. 解决这个问题加一个select判断
		select {
		case <-stopCh:
			log.Print("exit")
			return
		default: // 5.如果没有default会导致程序卡在上面直到stopCh关闭

		}
		time.Sleep(time.Second)
		log.Print("work hard...")
		select { // 3. 比较奇葩的是这里, select语句执行不是按照case顺序执行的, 而是当其中有一个可以取到值就执行, 一次只执行一个case; 当两个管道同时有值时
		// 他会随机选取一个(参考: https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-select/)
		case <-ticker.C:
			log.Print("Next")
		case <-stopCh:
			log.Print("exit")
			return
		}
	}
}

6.go内存对齐

参考: https://segmentfault.com/a/1190000017527311

type A1 struct {
	a int
	b string
	c int
	//d string
}

type A2 struct {
	a int
	c int
	b string
	//d string
}

type M1 struct {
	a int8
	b int64
	c int32
}

type M2 struct {
	a int8
	b int32
	c int64
}

func test7() {
	a1 := A1{
		a: 1,
		b: "a",
		c: 2,
		//d: "d",
	}
	a2 := A2{
		a: 1,
		c: 2,
		b: "a",
		//d: "d",
	}
	m1 := M1{
		a: 1,
		b: 2,
		c: 3,
	}
	m2 := M2{
		a: 1,
		b: 2,
		c: 3,
	}
	fmt.Println(unsafe.Sizeof(a1), unsafe.Sizeof(a2))
	fmt.Println(unsafe.Sizeof(m1), unsafe.Sizeof(m2))
}

7. go module declares问题你如何解决

go: finding module for package github.com/uber-go/ratelimit
go: downloading github.com/uber-go/ratelimit v0.2.0
go: finding module for package github.com/uber-go/ratelimit
go: found github.com/uber-go/ratelimit in github.com/uber-go/ratelimit v0.2.0
go: bluebell/middlewares imports
        github.com/uber-go/ratelimit: github.com/uber-go/ratelimit@v0.2.0: parsing go.mod:
        module declares its path as: go.uber.org/ratelimit
                but was required as: github.com/uber-go/ratelimit

参考:https://sunnyos.com/article-show-99.html


当然如果你不想看的的直接给你的go.mod文件中加一句: (写在require括号外面的地方, 执行go mod tidy即可)
  replace (直接把but was required as:后面的地址放这里) => (把module declares its path as:后面的地址放这里,记得加上版本)) // indirect

例子:
  replace github.com/uber-go/ratelimit v0.2.0 => go.uber.org/ratelimit v0.2.0 // indirect

其他(待整理)

// 1. new 和make的区别?
//new主要是为类型申请内存, 只会返回执行这个内存的指针
//make主要是对内置的channel和slice, map等结构进行初始化
// 区别主要是new返回指向内存的指针, make内置数据结构的初始化
//参考: https://draveness.me/golang/docs/part2-foundation/ch05-keyword/golang-make-and-new/
        https://mp.weixin.qq.com/s/xNdnVXxC5Ji2ApgbfpRaXQ

// 2. 内存管理?
// 3. Golang中参数传递值好还是指针好?
//参考: https://zhuanlan.zhihu.com/p/383007654

// 4. 线程, linux线程模型?
//参考: https://www.cnblogs.com/lxmhhy/p/6041001.html
// 5.Goroutine什么时候会发生阻塞?
// 6. PMG模型中Goroutine有哪几种状态?线程呢?每个线程/协程占用多少内存知道吗?
// 7. 如果Goroutine—直占用资源怎么办,PMG模型怎么解决的这个问题?
posted @   楠海  阅读(78)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示

目录导航