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