目录

  1. 实现一个可以从多个goroutine安全递增的并发计数器
  2. 编写一个函数从字符串解析JSON,并优雅地处理可能的错误
  3. 编写一个简单的HTTP服务器,用于处理请求,并向客户端发送问候消息
  4. 创建一个程序,其中两个goroutine使用一个通道来回传递一个整数,每次传递时增加该值,直到该值达到10,然后打印最终值
  5. 编写一个函数,使用反射来设置任何结构体中指定字段的值
  6. 实现一个简单的定时任务调度器,该调度器可以定时执行任务
  7. 编写一个函数,使用通道并行合并多个已排序的整数数组
  8. 编写一个支持并发读写的缓存系统,使用读写锁来提高性能
  9. 编写一个WebSocket服务端,用于实时广播消息给所有已连接的客户端
  10. 编写一个程序,使用goroutine并发下载多个文件,并将结果合并为一个文件

实现一个可以从多个goroutine安全递增的并发计数器

答案

package main

import (
    "fmt"
    "sync"
)

type Counter struct {
    value int
}

func (c *Counter) increment() {
    c.value++
}

func main() {
    var wg sync.WaitGroup
    counter := Counter{}
    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            counter.increment()
        }()
    }
    wg.Wait()
    fmt.Println("Counter value:", counter.value)
}

正误

错误

理由

  • 在 Counter 结构体中,value 字段没有使用互斥锁来同步访问,这可能会导致多个 goroutine 同时访问和修改 value 字段,从而产生竞态条件.
  • 在 increment 方法中,直接对 value 字段进行操作,没有使用互斥锁来同步访问.
  • 在 main 函数中,使用 wg.Add(1) 和 wg.Done() 来管理 goroutine 的执行,但是没有使用互斥锁来同步对 counter 的访问.

编写一个函数从字符串解析JSON,并优雅地处理可能的错误

答案

package main

import (
    "encoding/json"
    "fmt"
)

func parseJSON(input string) (map[string]interface{}, error) {
    var result map[string]interface{}
    err := json.Unmarshal([]byte(input), &result)
    if err != nil {
        return nil, fmt.Errorf("Failed to parse JSON: %s", err)
    }
    return result, nil
}

func main() {
    jsonStr := `{"name": "John", "age": 30}`
    result, err := parseJSON(jsonStr)
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    fmt.Println("Result:", result)
}

正误

错误

理由

  • 在 parseJSON 函数中,使用 json.Unmarshal 来解析 JSON 字符串并将其反序列化为 map[string]interface{} 类型.但是,如果输入的 JSON 字符串中包含无效的 JSON 数据,json.Unmarshal 可能会返回 interface {} 类型的值,而不是 map[string]interface{} 类型.
  • 在 main 函数中,使用 result, err := parseJSON(jsonStr) 来调用 parseJSON 函数并获取结果.如果解析失败,err 将包含错误信息,但是 result 可能包含无效的值,而不是 nil.

编写一个简单的HTTP服务器,用于处理请求,并向客户端发送问候消息

答案

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

正误

错误

理由

  • 在 handler 函数中,使用 fmt.Fprintf 来向客户端发送问候消息.但是,如果客户端发送的请求路径不包含任何额外路径(例如 /),r.URL.Path[1:] 可能会导致索引越界错误.
  • 在 main 函数中,使用 http.HandleFunc 来注册处理函数,并将根路径/映射到 handler 函数.但是,如果客户端发送的请求路径不包含任何额外路径,handler 函数中的 r.URL.Path[1:] 可能会导致空字符串,从而影响问候消息的格式.

创建一个程序,其中两个goroutine使用一个通道来回传递一个整数,每次传递时增加该值,直到该值达到10,然后打印最终值

答案

package main

import (
    "fmt"
)

func main() {
    ch := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            val := <-ch
            ch <- (val + 1)
        }
    }()
    ch <- 0
    final := <-ch
    fmt.Println("Final value:", final)
}

正误

错误

理由

  • 在 main 函数中,使用 ch <- 0 将初始值 0 发送给通道,但是没有等待另一个 goroutine 处理该值并发送回更新后的值.这可能会导致程序在另一个 goroutine 处理完之前就尝试读取最终值,从而得到不正确的结果.
  • 在另一个 goroutine 中,使用 for 循环来不断从通道中读取值,并将其增加 1 后发送回通道.但是,如果 main 函数中的代码在另一个 goroutine 处理完之前就尝试读取最终值,该循环可能会一直运行下去,导致程序无法终止.

编写一个函数,使用反射来设置任何结构体中指定字段的值

答案

package main

import (
    "fmt"
    "reflect"
)

type Person struct {
    Name string
    Age  int
}

func SetField(obj interface{}, name string, value interface{}) error {
    structValue := reflect.ValueOf(obj)
    fieldVal := structValue.FieldByName(name)
    if !fieldVal.IsValid() {
        return fmt.Errorf("No such field: %s in obj", name)
    }
    if !fieldVal.CanSet() {
        return fmt.Errorf("Cannot set %s field value", name)
    }

    val := reflect.ValueOf(value)
    fieldVal.Set(val)
    return nil
}

func main() {
    p := Person{Name: "John", Age: 30}
    SetField(&p, "Name", "David")
    fmt.Println(p)
}

正误

错误

理由

  • 在 SetField 函数中,使用 reflect.ValueOf(obj) 来获取对象的值,但是没有检查该值是否为 nil.如果传入的 obj 为 nil,则后续的操作可能会导致程序崩溃.
  • 在 SetField 函数中,使用 structValue.FieldByName(name) 来获取指定字段的值,但是没有检查该字段是否存在.如果传入的 name 不存在,则后续的操作可能会导致程序崩溃.
  • 在 main 函数中,使用 SetField(&p, "Name", "David") 来设置 Name 字段的值,但是没有检查返回的错误.如果设置失败,程序可能会继续执行,导致不正确的结果.

实现一个简单的定时任务调度器,该调度器可以定时执行任务

答案

package main

import (
    "fmt"
    "time"
)

func startScheduler(interval time.Duration, task func()) {
    ticker := time.NewTicker(interval)
    for {
        select {
        case <-ticker.C:
            go task()
        }
    }
}

func main() {
    startScheduler(1*time.Second, func() {
        fmt.Println("Task executed", time.Now())
    })
}

正误

错误

理由

  • 在 startScheduler 函数中,使用 select 语句监听 ticker.C 通道,当接收到来自 ticker.C 的消息时,会执行 task 函数.但是,如果 task 函数执行时间较长,可能会导致后续的 ticker.C 消息被阻塞,从而影响定时任务的执行间隔.
  • 在 main 函数中,使用 startScheduler 函数来启动定时任务调度器,并传递了一个匿名函数作为任务.但是,如果在执行任务时发生错误,该错误不会被处理,可能会导致程序崩溃.

编写一个函数,使用通道并行合并多个已排序的整数数组

答案

package main

import (
    "fmt"
    "sort"
)

func mergeArrays(channels ...<-chan int) <-chan int {
    out := make(chan int)
    go func() {
        var numbers []int
        for _, ch := range channels {
            for num := range ch {
                numbers = append(numbers, num)
            }
        }
        sort.Ints(numbers)
        for _, num := range numbers {
            out <- num
        }
        close(out)
    }()
    return out
}

func main() {
    c1 := make(chan int, 3)
    c2 := make(chan int, 3)
    c1 <- 1; c1 <- 4; c1 <- 6
    c2 <- 2; c2 <- 3; c2 <- 5
    close(c1)
    close(c2)

    for num := range mergeArrays(c1, c2) {
        fmt.Println(num)
    }
}

正误

错误

理由

  • 在 mergeArrays 函数中,使用 for ... range 循环读取每个输入通道中的整数时,没有在循环结束后关闭输入通道.这可能会导致资源泄漏.
  • 在 main 函数中,没有在发送整数到通道后立即关闭通道.这可能会导致 mergeArrays 函数中的 for ... range 循环一直等待,直到超时或被强制终止.

编写一个支持并发读写的缓存系统,使用读写锁来提高性能

答案

package main

import (
    "fmt"
    "sync"
)

type Cache struct {
    store map[string]string
    lock  sync.RWMutex
}

func (c *Cache) Get(key string) string {
    c.lock.RLock()
    value := c.store[key]
    c.lock.RUnlock()
    return value
}

func (c *Cache) Set(key string, value string) {
    c.lock.Lock()
    c.store[key] = value
    c.lock.Unlock()
}

func main() {
    c := Cache{store: make(map[string]string)}
    c.Set("a", "1")
    fmt.Println(c.Get("a"))
}

正误

正确

理由

可以使用defer释放锁

编写一个WebSocket服务端,用于实时广播消息给所有已连接的客户端

答案

package main

import (
    "log"
    "net/http"

    "github.com/gorilla/websocket"
)

var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan string)

var upgrader = websocket.Upgrader{}

func main() {
    http.HandleFunc("/ws", handleConnections)

    go handleMessages()

    log.Println("http server started on :8080")
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

func handleConnections(w http.ResponseWriter, r *http.Request) {
    ws, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer ws.Close()

    clients[ws] = true

    for {
        _, message, err := ws.ReadMessage()
        if err != nil {
            log.Printf("error: %v", err)
            delete(clients, ws)
            break
        }
        broadcast <- string(message)
    }
}

func handleMessages() {
    for msg := range broadcast {
        for client := range clients {
            err := client.WriteMessage(websocket.TextMessage, []byte(msg))
            if err != nil {
                log.Printf("error: %v", err)
                client.Close()
                delete(clients, client)
            }
        }
    }
}

正误

错误

理由

  • 在 handleConnections 函数中,当客户端发送消息时,会将其广播到所有客户端,包括发送消息的客户端本身.这可能不是预期的行为.
  • 在 handleConnections 函数中,如果读取客户端消息时发生错误,会直接删除该客户端,而不关闭连接.这可能会导致资源泄漏.
  • 在 handleMessages 函数中,如果向客户端发送消息时发生错误,会直接关闭该客户端,而不从 clients 映射中删除.这可能会导致重复关闭连接.

编写一个程序,使用goroutine并发下载多个文件,并将结果合并为一个文件

答案

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "sync"
)

func main() {
    urls := []string{
        "http://example.com/file1.txt",
        "http://example.com/file2.txt",
        "http://example.com/file3.txt",
    }

    var wg sync.WaitGroup
    for _, url := range urls {
        wg.Add(1)
        go func(url string) {
            defer wg.Done()
            // 下载文件
            resp, err := http.Get(url)
            if err != nil {
                fmt.Println("Error downloading:", url)
                return
            }
            defer resp.Body.Close()

            // 写入文件
            out, err := os.Create("result.txt")
            if err != nil {
                fmt.Println("Error creating file:", err)
                return
            }
            defer out.Close()

            _, err = io.Copy(out, resp.Body)
            if err != nil {
                fmt.Println("Error writing to file:", err)
                return
            }
        }(url)
    }
    wg.Wait()
    fmt.Println("Download completed.")
}

正误

错误

理由

应为os.OpenFile打开文件以实现 追加写 的功能