Golang 代码题-B版
Golang 代码题-B版
1. 设计一个线程安全的单例模式
问题描述
设计并实现一个线程安全的单例模式,需要满足以下要求:
- 支持延迟初始化
- 保证线程安全
- 确保高性能
- 支持并发访问
解题思路
这个问题考察了面试者对Go语言并发编程和设计模式的理解。主要实现方式包括:
- 使用 sync.Once 实现
- 使用双重检查锁实现
- 使用互斥锁实现
代码实现
// 使用sync.Once实现线程安全的单例模式
type Singleton struct {
data string
}
var (
instance *Singleton
once sync.Once
)
// GetInstance 返回单例实例
func GetInstance() *Singleton {
once.Do(func() {
instance = &Singleton{
data: "初始化数据",
}
})
return instance
}
// 使用双重检查锁实现
type Singleton2 struct {
data string
}
var (
instance2 *Singleton2
mu sync.Mutex
)
// GetInstance2 返回单例实例(双重检查锁)
func GetInstance2() *Singleton2 {
if instance2 == nil {
mu.Lock()
defer mu.Unlock()
if instance2 == nil {
instance2 = &Singleton2{
data: "初始化数据",
}
}
}
return instance2
}
// 测试代码
func TestSingleton() {
// 测试sync.Once实现
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
instance := GetInstance()
fmt.Printf("实例地址: %p\n", instance)
}()
}
wg.Wait()
// 测试双重检查锁实现
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
instance := GetInstance2()
fmt.Printf("实例地址: %p\n", instance)
}()
}
wg.Wait()
}复杂度分析
- 时间复杂度:O(1),获取实例的操作是常数时间
- 空间复杂度:O(1),只需要存储一个实例
- 并发性能:使用sync.Once的实现性能最好,因为once.Do()是原子操作
2. 设计一个生产者-消费者模型
问题描述
设计并实现一个生产者-消费者模型,需要满足以下要求:
- 支持多个生产者和消费者
- 使用channel进行通信
- 实现优雅退出
- 处理异常情况
解题思路
这个问题考察了面试者对Go语言并发编程和channel使用的理解。主要实现要点包括:
- 使用 channel 进行任务传递
- 使用 WaitGroup 进行并发控制
- 实现优雅退出机制
- 处理异常情况
代码实现
// 生产者消费者模型
type Producer struct {
id int
tasks chan<- Task
quit chan struct{}
wg *sync.WaitGroup
}
type Consumer struct {
id int
tasks <-chan Task
quit chan struct{}
wg *sync.WaitGroup
}
type Task struct {
ID int
Data string
Timestamp time.Time
}
// 创建生产者
func NewProducer(id int, tasks chan<- Task, wg *sync.WaitGroup) *Producer {
return &Producer{
id: id,
tasks: tasks,
quit: make(chan struct{}),
wg: wg,
}
}
// 创建消费者
func NewConsumer(id int, tasks <-chan Task, wg *sync.WaitGroup) *Consumer {
return &Consumer{
id: id,
tasks: tasks,
quit: make(chan struct{}),
wg: wg,
}
}
// 生产者运行
func (p *Producer) Run() {
defer p.wg.Done()
for {
select {
case <-p.quit:
fmt.Printf("生产者 %d 退出\n", p.id)
return
default:
task := Task{
ID: p.id,
Data: fmt.Sprintf("数据-%d", time.Now().UnixNano()),
Timestamp: time.Now(),
}
select {
case p.tasks <- task:
fmt.Printf("生产者 %d 生产任务: %+v\n", p.id, task)
case <-p.quit:
return
}
time.Sleep(time.Millisecond * 100)
}
}
}
// 消费者运行
func (c *Consumer) Run() {
defer c.wg.Done()
for {
select {
case <-c.quit:
fmt.Printf("消费者 %d 退出\n", c.id)
return
case task, ok := <-c.tasks:
if !ok {
return
}
fmt.Printf("消费者 %d 处理任务: %+v\n", c.id, task)
time.Sleep(time.Millisecond * 200)
}
}
}
// 停止生产者
func (p *Producer) Stop() {
close(p.quit)
}
// 停止消费者
func (c *Consumer) Stop() {
close(c.quit)
}
// 测试代码
func TestProducerConsumer() {
// 创建任务通道
tasks := make(chan Task, 10)
// 创建WaitGroup
var wg sync.WaitGroup
// 创建生产者和消费者
producers := make([]*Producer, 3)
consumers := make([]*Consumer, 2)
// 启动生产者
for i := 0; i < 3; i++ {
producers[i] = NewProducer(i, tasks, &wg)
wg.Add(1)
go producers[i].Run()
}
// 启动消费者
for i := 0; i < 2; i++ {
consumers[i] = NewConsumer(i, tasks, &wg)
wg.Add(1)
go consumers[i].Run()
}
// 运行一段时间后停止
time.Sleep(time.Second * 5)
// 停止生产者
for _, p := range producers {
p.Stop()
}
// 停止消费者
for _, c := range consumers {
c.Stop()
}
// 关闭任务通道
close(tasks)
// 等待所有goroutine退出
wg.Wait()
}复杂度分析
- 时间复杂度:O(n),n为任务数量
- 空间复杂度:O(m),m为channel缓冲区大小
- 并发性能:支持多个生产者和消费者并发处理
3. 设计一个简单的内存缓存
问题描述
设计并实现一个简单的内存缓存,需要满足以下要求:
- 支持基本的CRUD操作
- 支持过期时间
- 支持并发访问
- 支持容量限制
解题思路
这个问题考察了面试者对Go语言并发编程和数据结构的使用。主要实现要点包括:
- 使用 map 存储数据
- 实现过期机制
- 使用读写锁保证并发安全
- 实现容量限制和淘汰策略
代码实现
// 缓存项
type CacheItem struct {
Key string
Value interface{}
ExpiresAt time.Time
}
// 内存缓存
type Cache struct {
items map[string]*CacheItem
mu sync.RWMutex
maxSize int
cleanup *time.Ticker
done chan struct{}
}
// 创建缓存
func NewCache(maxSize int) *Cache {
c := &Cache{
items: make(map[string]*CacheItem),
maxSize: maxSize,
cleanup: time.NewTicker(time.Minute),
done: make(chan struct{}),
}
go c.cleanupExpired()
return c
}
// 设置缓存
func (c *Cache) Set(key string, value interface{}, ttl time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
// 检查容量
if len(c.items) >= c.maxSize {
c.evictOldest()
}
c.items[key] = &CacheItem{
Key: key,
Value: value,
ExpiresAt: time.Now().Add(ttl),
}
}
// 获取缓存
func (c *Cache) Get(key string) (interface{}, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
item, exists := c.items[key]
if !exists {
return nil, false
}
if time.Now().After(item.ExpiresAt) {
delete(c.items, key)
return nil, false
}
return item.Value, true
}
// 删除缓存
func (c *Cache) Delete(key string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.items, key)
}
// 清理过期项
func (c *Cache) cleanupExpired() {
for {
select {
case <-c.cleanup.C:
c.mu.Lock()
now := time.Now()
for key, item := range c.items {
if now.After(item.ExpiresAt) {
delete(c.items, key)
}
}
c.mu.Unlock()
case <-c.done:
return
}
}
}
// 淘汰最旧的项
func (c *Cache) evictOldest() {
var oldestKey string
var oldestTime time.Time
for key, item := range c.items {
if oldestKey == "" || item.ExpiresAt.Before(oldestTime) {
oldestKey = key
oldestTime = item.ExpiresAt
}
}
if oldestKey != "" {
delete(c.items, oldestKey)
}
}
// 关闭缓存
func (c *Cache) Close() {
c.cleanup.Stop()
close(c.done)
}
// 测试代码
func TestCache() {
cache := NewCache(100)
defer cache.Close()
// 设置缓存
cache.Set("key1", "value1", time.Second*5)
cache.Set("key2", "value2", time.Second*10)
// 获取缓存
if value, exists := cache.Get("key1"); exists {
fmt.Printf("key1: %v\n", value)
}
// 等待过期
time.Sleep(time.Second * 6)
// 检查过期
if _, exists := cache.Get("key1"); !exists {
fmt.Println("key1已过期")
}
// 并发测试
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
key := fmt.Sprintf("key%d", i)
cache.Set(key, fmt.Sprintf("value%d", i), time.Second*5)
if value, exists := cache.Get(key); exists {
fmt.Printf("%s: %v\n", key, value)
}
}(i)
}
wg.Wait()
}复杂度分析
- 时间复杂度:
- 设置:O(1)
- 获取:O(1)
- 删除:O(1)
- 清理:O(n),n为缓存项数量
- 空间复杂度:O(n),n为缓存项数量
- 并发性能:使用读写锁保证并发安全
4. 设计一个简单的限流器
问题描述
设计并实现一个简单的限流器,需要满足以下要求:
- 支持令牌桶算法
- 支持并发访问
- 支持动态调整速率
- 支持超时控制
解题思路
这个问题考察了面试者对Go语言并发编程和限流算法的理解。主要实现要点包括:
- 实现令牌桶算法
- 使用互斥锁保证并发安全
- 支持动态调整速率
- 实现超时控制机制
代码实现
// 令牌桶限流器
type RateLimiter struct {
rate float64 // 令牌产生速率
capacity int64 // 桶容量
tokens float64 // 当前令牌数
lastTime time.Time // 上次更新时间
mu sync.Mutex // 互斥锁
}
// 创建限流器
func NewRateLimiter(rate float64, capacity int64) *RateLimiter {
return &RateLimiter{
rate: rate,
capacity: capacity,
tokens: float64(capacity),
lastTime: time.Now(),
}
}
// 获取令牌
func (rl *RateLimiter) Allow() bool {
rl.mu.Lock()
defer rl.mu.Unlock()
now := time.Now()
// 计算时间间隔内产生的令牌
elapsed := now.Sub(rl.lastTime).Seconds()
rl.tokens = math.Min(float64(rl.capacity), rl.tokens+elapsed*rl.rate)
rl.lastTime = now
if rl.tokens >= 1 {
rl.tokens--
return true
}
return false
}
// 带超时的获取令牌
func (rl *RateLimiter) AllowWithTimeout(timeout time.Duration) bool {
if rl.Allow() {
return true
}
timer := time.NewTimer(timeout)
defer timer.Stop()
ticker := time.NewTicker(time.Millisecond * 100)
defer ticker.Stop()
for {
select {
case <-timer.C:
return false
case <-ticker.C:
if rl.Allow() {
return true
}
}
}
}
// 动态调整速率
func (rl *RateLimiter) SetRate(rate float64) {
rl.mu.Lock()
defer rl.mu.Unlock()
rl.rate = rate
}
// 测试代码
func TestRateLimiter() {
// 创建限流器:每秒10个令牌,桶容量20
limiter := NewRateLimiter(10, 20)
// 测试基本限流
for i := 0; i < 30; i++ {
if limiter.Allow() {
fmt.Printf("请求 %d 通过\n", i)
} else {
fmt.Printf("请求 %d 被限流\n", i)
}
time.Sleep(time.Millisecond * 100)
}
// 测试超时控制
fmt.Println("\n测试超时控制:")
for i := 0; i < 5; i++ {
if limiter.AllowWithTimeout(time.Second) {
fmt.Printf("请求 %d 在超时前通过\n", i)
} else {
fmt.Printf("请求 %d 超时\n", i)
}
}
// 测试动态调整速率
fmt.Println("\n调整速率后测试:")
limiter.SetRate(20) // 提高速率到每秒20个令牌
for i := 0; i < 10; i++ {
if limiter.Allow() {
fmt.Printf("请求 %d 通过\n", i)
} else {
fmt.Printf("请求 %d 被限流\n", i)
}
time.Sleep(time.Millisecond * 50)
}
}复杂度分析
- 时间复杂度:
- 获取令牌:O(1)
- 带超时获取:O(n),n为尝试次数
- 空间复杂度:O(1)
- 并发性能:使用互斥锁保证并发安全
5. 设计一个简单的对象池
问题描述
设计并实现一个简单的对象池,需要满足以下要求:
- 支持对象的获取和归还
- 支持对象的最大数量限制
- 支持对象的创建和销毁
- 支持并发访问
解题思路
这个问题考察了面试者对Go语言并发编程和资源管理的理解。主要实现要点包括:
- 使用 channel 管理对象池
- 实现对象的创建和复用
- 使用互斥锁保证并发安全
- 实现超时获取机制
代码实现
// 对象池
type Pool struct {
objects chan interface{}
factory func() interface{}
maxSize int
mu sync.Mutex
size int
}
// 创建对象池
func NewPool(factory func() interface{}, maxSize int) *Pool {
return &Pool{
objects: make(chan interface{}, maxSize),
factory: factory,
maxSize: maxSize,
}
}
// 获取对象
func (p *Pool) Get() interface{} {
select {
case obj := <-p.objects:
return obj
default:
p.mu.Lock()
defer p.mu.Unlock()
if p.size < p.maxSize {
p.size++
return p.factory()
}
// 等待对象可用
return <-p.objects
}
}
// 带超时的获取对象
func (p *Pool) GetWithTimeout(timeout time.Duration) (interface{}, bool) {
select {
case obj := <-p.objects:
return obj, true
case <-time.After(timeout):
return nil, false
}
}
// 归还对象
func (p *Pool) Put(obj interface{}) {
select {
case p.objects <- obj:
// 成功归还
default:
// 池已满,丢弃对象
p.mu.Lock()
p.size--
p.mu.Unlock()
}
}
// 测试代码
func TestPool() {
// 创建对象池
pool := NewPool(func() interface{} {
return &struct {
ID int
Timestamp time.Time
}{
ID: time.Now().Nanosecond(),
Timestamp: time.Now(),
}
}, 5)
// 测试基本操作
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
// 获取对象
obj := pool.Get()
fmt.Printf("协程 %d 获取对象: %+v\n", id, obj)
// 使用对象
time.Sleep(time.Millisecond * 100)
// 归还对象
pool.Put(obj)
fmt.Printf("协程 %d 归还对象\n", id)
}(i)
}
wg.Wait()
// 测试超时获取
fmt.Println("\n测试超时获取:")
for i := 0; i < 3; i++ {
if obj, ok := pool.GetWithTimeout(time.Millisecond * 100); ok {
fmt.Printf("成功获取对象: %+v\n", obj)
pool.Put(obj)
} else {
fmt.Println("获取对象超时")
}
}
}复杂度分析
- 时间复杂度:
- 获取对象:O(1)
- 归还对象:O(1)
- 带超时获取:O(1)
- 空间复杂度:O(n),n为池的最大容量
- 并发性能:使用channel和互斥锁保证并发安全
6. 设计一个简单的任务调度器
问题描述
设计并实现一个简单的任务调度器,需要满足以下要求:
- 支持定时任务
- 支持周期性任务
- 支持任务取消
- 支持并发执行
解题思路
这个问题考察了面试者对Go语言并发编程和任务调度的理解。主要实现要点包括:
- 实现任务接口
- 使用定时器进行调度
- 实现任务取消机制
- 使用 WaitGroup 控制并发
代码实现
// 任务接口
type Task interface {
Execute() error
GetID() string
}
// 基础任务
type BaseTask struct {
ID string
Interval time.Duration
LastRun time.Time
NextRun time.Time
Stop chan struct{}
}
// 任务调度器
type Scheduler struct {
tasks map[string]Task
mu sync.RWMutex
stop chan struct{}
wg sync.WaitGroup
}
// 创建调度器
func NewScheduler() *Scheduler {
return &Scheduler{
tasks: make(map[string]Task),
stop: make(chan struct{}),
}
}
// 添加任务
func (s *Scheduler) AddTask(task Task, interval time.Duration) {
s.mu.Lock()
defer s.mu.Unlock()
baseTask := &BaseTask{
ID: task.GetID(),
Interval: interval,
NextRun: time.Now().Add(interval),
Stop: make(chan struct{}),
}
s.tasks[task.GetID()] = task
s.wg.Add(1)
go s.scheduleTask(task, baseTask)
}
// 调度任务
func (s *Scheduler) scheduleTask(task Task, baseTask *BaseTask) {
defer s.wg.Done()
ticker := time.NewTicker(time.Millisecond * 100)
defer ticker.Stop()
for {
select {
case <-s.stop:
return
case <-baseTask.Stop:
return
case <-ticker.C:
if time.Now().After(baseTask.NextRun) {
s.wg.Add(1)
go func() {
defer s.wg.Done()
if err := task.Execute(); err != nil {
fmt.Printf("任务 %s 执行错误: %v\n", task.GetID(), err)
}
}()
baseTask.LastRun = time.Now()
baseTask.NextRun = time.Now().Add(baseTask.Interval)
}
}
}
}
// 取消任务
func (s *Scheduler) CancelTask(taskID string) {
s.mu.Lock()
defer s.mu.Unlock()
if task, exists := s.tasks[taskID]; exists {
if baseTask, ok := task.(*BaseTask); ok {
close(baseTask.Stop)
}
delete(s.tasks, taskID)
}
}
// 停止调度器
func (s *Scheduler) Stop() {
close(s.stop)
s.wg.Wait()
}
// 测试代码
func TestScheduler() {
// 创建调度器
scheduler := NewScheduler()
// 创建任务
task1 := &BaseTask{ID: "task1"}
task2 := &BaseTask{ID: "task2"}
// 添加任务
scheduler.AddTask(task1, time.Second)
scheduler.AddTask(task2, time.Second*2)
// 运行一段时间
time.Sleep(time.Second * 5)
// 取消任务
scheduler.CancelTask("task1")
// 继续运行
time.Sleep(time.Second * 5)
// 停止调度器
scheduler.Stop()
}
// 自定义任务示例
type CustomTask struct {
*BaseTask
Name string
}
func (t *CustomTask) Execute() error {
fmt.Printf("执行任务 %s: %s\n", t.ID, t.Name)
return nil
}
func (t *CustomTask) GetID() string {
return t.ID
}
// 使用示例
func Example() {
scheduler := NewScheduler()
// 创建自定义任务
task1 := &CustomTask{
BaseTask: &BaseTask{ID: "task1"},
Name: "任务1",
}
task2 := &CustomTask{
BaseTask: &BaseTask{ID: "task2"},
Name: "任务2",
}
// 添加任务
scheduler.AddTask(task1, time.Second)
scheduler.AddTask(task2, time.Second*2)
// 运行一段时间
time.Sleep(time.Second * 10)
// 停止调度器
scheduler.Stop()
}复杂度分析
- 时间复杂度:
- 添加任务:O(1)
- 取消任务:O(1)
- 任务调度:O(n),n为任务数量
- 空间复杂度:O(n),n为任务数量
- 并发性能:使用互斥锁和WaitGroup保证并发安全
7. 使用三个协程顺序打印 aa bb cc
问题描述
使用三个协程,每秒钟打印aa bb cc,要求:
- 顺序不能变化
- 协程1打印aa
- 协程2打印bb
- 协程3打印cc
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 使用三个channel控制打印顺序
- 使用WaitGroup等待协程完成
- 通过channel传递信号实现顺序控制
代码实现
package main
import (
"fmt"
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(3)
// 使用带缓冲的channel,避免死锁
ch1 := make(chan struct{}, 1)
ch2 := make(chan struct{}, 1)
ch3 := make(chan struct{}, 1)
// 启动三个协程
go func() {
defer wg.Done()
for {
<-ch1
fmt.Println("aa")
time.Sleep(time.Second)
ch2 <- struct{}{}
}
}()
go func() {
defer wg.Done()
for {
<-ch2
fmt.Println("bb")
time.Sleep(time.Second)
ch3 <- struct{}{}
}
}()
go func() {
defer wg.Done()
for {
<-ch3
fmt.Println("cc")
time.Sleep(time.Second)
ch1 <- struct{}{}
}
}()
// 启动打印
ch1 <- struct{}{}
wg.Wait()
}8. 设计两个协程交替输出字母和数字
问题描述
设计两个协程交替输出A 1 B 2 C 3 .... Z 26,要求:
- 字母和数字交替输出
- 按照字母表顺序输出
- 数字从1开始递增
- 使用channel控制输出顺序
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 使用两个带缓冲的channel控制打印顺序
- 一个协程负责打印字母
- 一个协程负责打印数字
- 通过channel传递信号实现交替打印
代码实现
package main
import (
"fmt"
"time"
)
func main() {
// 使用带缓冲的channel
letterChan := make(chan bool, 1)
numberChan := make(chan bool, 1)
// 打印字母的协程
go func() {
for i := 'A'; i <= 'Z'; i++ {
<-letterChan
fmt.Printf("%c ", i)
numberChan <- true
}
}()
// 打印数字的协程
go func() {
for i := 1; i <= 26; i++ {
<-numberChan
fmt.Printf("%d ", i)
if i < 26 {
letterChan <- true
}
}
}()
// 启动打印
letterChan <- true
time.Sleep(time.Second)
fmt.Println()
}9. 设计N个协程顺序打印数字
问题描述
设计N个协程顺序打印数字,要求:
- 创建N个协程
- 按顺序打印数字
- 每个协程负责打印自己的数字
- 使用channel控制打印顺序
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 创建N个channel控制打印顺序
- 使用channel传递数字
- 主协程负责发送数字
- 工作协程负责打印数字
代码实现
package main
import (
"fmt"
"time"
)
func main() {
const n = 5 // 协程数量
const max = 100 // 最大数字
// 创建n个channel
chans := make([]chan int, n)
for i := range chans {
chans[i] = make(chan int)
}
// 启动n个协程
for i := 0; i < n; i++ {
go func(id int) {
for num := range chans[id] {
fmt.Printf("协程%d打印: %d\n", id, num)
time.Sleep(time.Millisecond * 100)
}
}(i)
}
// 主协程发送数字
for i := 0; i < max; i++ {
chans[i%n] <- i
}
// 关闭所有channel
for i := range chans {
close(chans[i])
}
time.Sleep(time.Second)
}10. 分析切片底层原理
问题描述
分析下面函数的执行结果,考察对切片底层原理的理解:
- 切片的长度和容量
- 切片的扩容机制
- 切片的传递机制
- 子切片的特性
解题思路
这个问题考察了面试者对Go语言切片底层原理的理解。主要实现要点包括:
- 理解切片的底层数据结构
- 理解切片的扩容机制
- 理解切片的传递方式
代码实现
package main
import "fmt"
func main() {
// 创建一个长度为5,容量为10的切片
s := make([]int, 5, 10)
fmt.Printf("初始状态: len=%d cap=%d\n", len(s), cap(s))
// 修改切片
modifySlice(s)
fmt.Printf("修改后: len=%d cap=%d\n", len(s), cap(s))
// 追加元素
s = append(s, 1, 2, 3)
fmt.Printf("追加后: len=%d cap=%d\n", len(s), cap(s))
// 子切片
subSlice := s[2:4]
fmt.Printf("子切片: len=%d cap=%d\n", len(subSlice), cap(subSlice))
}
func modifySlice(s []int) {
s[0] = 100
s = append(s, 999)
fmt.Printf("函数内: len=%d cap=%d\n", len(s), cap(s))
}11. 分析GMP模型原理
问题描述
分析下面代码的输出,考察对GMP模型的理解:
- GMP模型的基本概念
- Goroutine的调度机制
- P的数量对调度的影响
- 调度器的实现原理
解题思路
这个问题考察了面试者对Go语言GMP模型的理解。主要实现要点包括:
- 理解GMP模型的基本概念
- 理解Goroutine的调度机制
- 理解P的数量对调度的影响
代码实现
package main
import (
"runtime"
"sync"
"time"
)
func main() {
// 设置P的数量为1
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(5)
// 启动5个goroutine
for i := 1; i <= 5; i++ {
go func(id int) {
defer wg.Done()
// 模拟一些计算
time.Sleep(time.Millisecond * 100)
fmt.Printf("goroutine %d 执行完成\n", id)
}(i)
}
// 等待所有goroutine完成
wg.Wait()
fmt.Println("所有goroutine执行完毕")
}12. 设计协程计算数组奇偶数和
问题描述
设计两个协程计算数组奇偶数和,要求:
- 使用channel机制
- 并发计算
- 等待所有计算完成
- 处理异常情况
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 使用channel传递计算结果
- 并发计算奇偶数和
- 等待所有计算完成
代码实现
package main
import (
"fmt"
"sync"
)
func main() {
// 创建测试数组
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 创建结果channel
resultChan := make(chan map[string]int)
// 启动计算协程
go func() {
result := make(map[string]int)
var wg sync.WaitGroup
wg.Add(2)
// 计算偶数和
go func() {
defer wg.Done()
sum := 0
for _, v := range arr {
if v%2 == 0 {
sum += v
}
}
result["even"] = sum
}()
// 计算奇数和
go func() {
defer wg.Done()
sum := 0
for _, v := range arr {
if v%2 != 0 {
sum += v
}
}
result["odd"] = sum
}()
wg.Wait()
resultChan <- result
}()
// 获取结果
result := <-resultChan
fmt.Printf("偶数和: %d\n", result["even"])
fmt.Printf("奇数和: %d\n", result["odd"])
}13. 设计生产者消费者模型
问题描述
设计10个生产者5个消费者的模型,要求:
- 生产者总共生产1000个消费物料(编号1-1000)
- 5个消费者并行消费
- 使用channel进行通信
- 实现优雅退出
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 使用channel传递任务
- 使用WaitGroup控制并发
- 实现优雅退出机制
- 处理异常情况
代码实现
package main
import (
"fmt"
"sync"
"time"
)
func main() {
const (
producerCount = 10
consumerCount = 5
totalItems = 1000
)
// 创建任务通道和结果通道
taskChan := make(chan int, totalItems)
doneChan := make(chan struct{})
var wg sync.WaitGroup
// 启动生产者
wg.Add(producerCount)
for i := 0; i < producerCount; i++ {
go func(id int) {
defer wg.Done()
for j := 1; j <= totalItems/producerCount; j++ {
item := (id * (totalItems / producerCount)) + j
taskChan <- item
fmt.Printf("生产者%d生产: %d\n", id, item)
time.Sleep(time.Millisecond * 10)
}
}(i)
}
// 启动消费者
wg.Add(consumerCount)
for i := 0; i < consumerCount; i++ {
go func(id int) {
defer wg.Done()
for {
select {
case item, ok := <-taskChan:
if !ok {
return
}
fmt.Printf("消费者%d消费: %d\n", id, item)
time.Sleep(time.Millisecond * 50)
case <-doneChan:
return
}
}
}(i)
}
// 等待所有生产者完成
wg.Wait()
close(taskChan)
// 等待一段时间后关闭消费者
time.Sleep(time.Second)
close(doneChan)
}14. 设计协程随机数加法
问题描述
设计多个协程执行随机数加法,要求:
- 使用多个协程生成随机数
- 计算随机数之和
- 找出最大值
- 使用channel收集结果
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 使用协程生成随机数
- 使用channel收集结果
- 计算最大值
- 等待所有协程完成
代码实现
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
func main() {
const (
goroutineCount = 10
maxNumber = 100
)
// 设置随机数种子
rand.Seed(time.Now().UnixNano())
// 创建结果通道
resultChan := make(chan int, goroutineCount)
var wg sync.WaitGroup
// 启动多个协程
wg.Add(goroutineCount)
for i := 0; i < goroutineCount; i++ {
go func() {
defer wg.Done()
// 生成两个随机数并计算和
a := rand.Intn(maxNumber)
b := rand.Intn(maxNumber)
sum := a + b
fmt.Printf("随机数: %d + %d = %d\n", a, b, sum)
resultChan <- sum
}()
}
// 等待所有协程完成
wg.Wait()
close(resultChan)
// 找出最大值
max := 0
for sum := range resultChan {
if sum > max {
max = sum
}
}
fmt.Printf("最大值为: %d\n", max)
}15. 设计交替打印奇偶数
问题描述
设计两个协程交替打印1-100之间的奇数和偶数,要求:
- 按照从小到大输出
- 奇数和偶数交替打印
- 使用channel控制打印顺序
- 等待所有打印完成
解题思路
这个问题考察了面试者对Go语言协程和channel的使用。主要实现要点包括:
- 使用channel控制打印顺序
- 一个协程打印奇数
- 一个协程打印偶数
- 使用WaitGroup等待完成
代码实现
package main
import (
"fmt"
"sync"
)
func main() {
const maxNum = 100
// 创建信号通道
oddReady := make(chan struct{})
evenReady := make(chan struct{})
var wg sync.WaitGroup
wg.Add(2)
// 打印奇数
go func() {
defer wg.Done()
for i := 1; i <= maxNum; i += 2 {
<-oddReady
fmt.Printf("奇数: %d\n", i)
if i < maxNum {
evenReady <- struct{}{}
}
}
}()
// 打印偶数
go func() {
defer wg.Done()
for i := 2; i <= maxNum; i += 2 {
<-evenReady
fmt.Printf("偶数: %d\n", i)
if i < maxNum {
oddReady <- struct{}{}
}
}
}()
// 启动打印
oddReady <- struct{}{}
wg.Wait()
}16. 设计多协程顺序打印数字
问题描述
设计10个协程顺序打印数字,要求:
- 每个协程只能打印最后一位是自己id号的数字
- 依次打印1-10000
- 使用条件变量控制打印顺序
- 实现优雅退出
解题思路
这个问题考察了面试者对Go语言协程和同步原语的使用。主要实现要点包括:
- 使用条件变量控制打印顺序
- 使用互斥锁保护共享数据
- 使用WaitGroup等待完成
- 实现优雅退出
代码实现
package main
import (
"fmt"
"sync"
)
func main() {
const (
maxNumber = 10000
workerCount = 10
)
var (
mu sync.Mutex
cond = sync.NewCond(&mu)
current = 0
wg sync.WaitGroup
)
wg.Add(workerCount)
// 创建workerCount个工作协程
for i := 0; i < workerCount; i++ {
go func(id int) {
defer wg.Done()
for {
mu.Lock()
// 等待轮到当前协程打印
for current < maxNumber && (current+1)%workerCount != id {
cond.Wait()
}
// 检查是否完成所有数字
if current >= maxNumber {
mu.Unlock()
return
}
// 打印数字
current++
fmt.Printf("worker %d 打印: %d\n", id, current)
// 通知下一个协程
cond.Broadcast()
mu.Unlock()
}
}(i)
}
wg.Wait()
fmt.Println("打印完成")
}