杨 发布的文章

题目:一个goroutine打印0-100,一个goroutine打印a-z,两个goroutine交替打印。
考点:用channel通知另一个goroutine,并且不能出现goroutine阻塞。
核心原理:channel 是同步阻塞的
在没有缓冲的 channel(或容量已满的 channel)中:

发送方(chan <- val)会阻塞,直到有接收方准备好读取。

接收方(<-chan)也会阻塞,直到有发送方发来数据。

这种阻塞机制就可以天然地用于控制两个 goroutine 的执行顺序。

方案一:
两个channel,一个用来控制打印数字,一个用来控制打印字母,goroutine先接收channel再开始打印,如果没有接收到信号就会阻塞,打印完再作为发送方向对方的channel发送信号,并且第二个goroutine打印完就不要再向channel发送信号了,否则会死锁。因为第一个goroutine已经打印完,不会再接收channel,导致goroutine死锁。

func Printnumandstring() {
    chNumber := make(chan struct{})
    chStr := make(chan struct{})
    wg := &sync.WaitGroup{}
    wg.Add(2)

    go func() {
        defer wg.Done()
        i := 0
        for i <= 100 {
            <-chNumber
            for j := 0; j < 4 && i <= 100; j++ {
                println(i)
                i++
            }
            chStr <- struct{}{}
        }
    }()

    go func() {
        defer wg.Done()
        for i := 'a'; i <= 'z'; i++ {
            <-chStr
            println(string(i))
            if i != 'z' {
                chNumber <- struct{}{}
            }
        }
    }()

    chNumber <- struct{}{}
    wg.Wait()
}

ch := make(chan int, 10)
    for i := 0; i <= 10; i++ {
        ch <- i
    }
    go func() {
        for v := range ch {
            fmt.Println(v)
        }
    }()

这是一道面试题,问这段代码运行结果。

这段代码大体可以分为两部分,主协程创建一个缓冲区大小为10的int类型的channel并且循环写入0-10共11个元素,子协程消费channel并打印。

对channel熟悉的人可能会发现,缓冲区大小小于写入元素个数,写完9后channel会阻塞主,10无法再写入。再往下执行可能就不确定了,感觉goroutine会打印0-9。恭喜你,已经踩到坑了。

这段代码的坑就在于goroutine执行在后面,当主协程向channel写满10个后就已经阻塞程序执行了,也就不会走到goroutine消费这一步骤,最终导致程序卡死,报出fatal错误:fatal error: all goroutines are asleep - deadlock!

场景1:实现并发调用多个方法,有一个返回结果就都返回
思路:可以使用 select 语句结合通道来等待多个 goroutine 返回结果,一旦有 goroutine 返回结果,就立即处理该结果并结束程序。

/**
 * @desc
 * @date 2025/4/19
 * @user yangshuo
 */

package main

import (
    "fmt"
    "time"
)

func https(resultChan chan string) {
    // 模拟 HTTP 请求
    // t := time.NewTicker(time.Duration(rand.Intn(5)+1) * time.Second)

    time.Sleep(time.Duration(4+1) * time.Second)
    resultChan <- "http"
    println("1")
}

func redis(resultChan chan string) {
    // 模拟 HTTP 请求
    // t := time.NewTicker(time.Duration(rand.Intn(5)+1) * time.Second)
    // <-t.C
    time.Sleep(time.Duration(5+1) * time.Second)
    resultChan <- "redis"
    println("2")
}

func mysql(resultChan chan string) {
    // 模拟 HTTP 请求
    // t := time.NewTicker(time.Duration(rand.Intn(5)+1) * time.Second)
    // <-t.C
    time.Sleep(time.Duration(3+1) * time.Second)
    resultChan <- "mysql"
    println("3")
}

func main() {
    resultChan := make(chan string)
    defer close(resultChan)

    go https(resultChan)
    go mysql(resultChan)
    go redis(resultChan)

    for {
        select {
        case result := <-resultChan:
            fmt.Println("返回结果:", result)
            return
        }
    }

}

场景2:实现并发调用多个校验方法,有一个成功就返回成功,所有方法都失败就返回失败。
思路:与场景1不同的是,失败情况需要等待所有goroutine都执行完。

/**
 * @desc
 * @date 2025/4/20
 * @user yangshuo
 */

package main

import (
    "fmt"
    "math/rand"
    "time"
)

func validateFunc1() bool {
    // 模拟验证逻辑
    // 设置随机种子
    return false
    rand.Seed(time.Now().UnixNano())

    // 生成随机数(0 或 1)
    randomNum := rand.Intn(2)

    // 根据随机数返回 true 或 false
    result := randomNum == 1
    fmt.Println(result)
    return result
}

func validateFunc2() bool {
    return false
    // 设置随机种子
    rand.Seed(time.Now().UnixNano())

    // 生成随机数(0 或 1)
    randomNum := rand.Intn(2)

    // 根据随机数返回 true 或 false
    result := randomNum == 1
    fmt.Println(result)
    return result
}

func validateFunc3() bool {
    return false
    // 模拟验证逻辑
    // 设置随机种子
    rand.Seed(time.Now().UnixNano())

    // 生成随机数(0 或 1)
    randomNum := rand.Intn(2)

    // 根据随机数返回 true 或 false
    result := randomNum == 1
    fmt.Println(result)
    return result
}

func main() {
    // 定义验证函数
    validateFuncs := []func() bool{validateFunc1, validateFunc2, validateFunc3}
    // 使用 channel 通知是否有函数通过验证
    passed := make(chan bool)

    // 并发调用验证函数
    for _, vf := range validateFuncs {
        go func(fn func() bool) {
            if fn() {
                passed <- true
            }
        }(vf)
    }

    // 检查是否有函数通过验证
    for range validateFuncs {
        select {
        case <-passed:
            fmt.Println("Validation passed")
            return
    }

    fmt.Println("Validation failed")
}

需要注意的是:当所有 goroutines 都未通过验证时,由于没有 goroutine 向 passed 通道发送消息,select 语句会一直阻塞等待消息,从而导致程序发生死锁。为了避免这种情况,可以加上default分支。

// 检查是否有函数通过验证
    for range validateFuncs {
        select {
        case <-passed:
            fmt.Println("Validation passed")
            return
        default:

        }
    }

实例代码

func doPatch(
    ctx *gin.Context,
    addrs []string,
    request *graph_raw.SmartAmountListReq, txnCnt, rival *int64, level uint, sTime int64) ([]graph_raw.CapitalAddressEdge, error) {
    wg := &sync.WaitGroup{}
    mutex := sync.Mutex{}
    res := make([]graph_raw.CapitalAddressEdge, 0)
    limit := make(chan bool, 5)
    
    for i := 0; i < len(addrs); i += 800 {
        wg.Add(1)
        start := i
        go func() {
            defer wg.Done()
            limit <- true
            group := collections.SliceSubRange(addrs, start, 800)
            edgeList, _ := multiGetAddressData(ctx, group, request)
            <-limit
            mutex.Lock()
            defer mutex.Unlock()
            for _, v := range edgeList {
                *txnCnt += int64(v.Count)
            }
            *rival += int64(len(edgeList))
            slog.Infof(ctx, "req_level: %d, curr_level: %d, txnCnt : %d, rival: %d, stime: %d ", request.Level, level, *txnCnt, *rival, sTime)

            res = append(res, edgeList...)
        }()
        cTime := time.Now().Unix()
        timeout := int64(9 * 60)
        totalTime := cTime - sTime
        slog.Infof(ctx, "curr_time: %d, s_time: %d, total_time: %d", cTime, sTime, totalTime)
        if totalTime >= timeout {
            return res, serror.CustomErr("查询超时", serror.CustomErrCode)
        }
    }
    wg.Wait()
    return res, nil
}

这段代码使用gorouting、waitgroup、sync.Mutex、channel,来实现并发下安全控制,

func doPatch(
    ctx *gin.Context,
    addrs []string,
    request *graph_raw.SmartAmountListReq, txnCnt, rival *int64, level uint, sTime int64) ([]graph_raw.CapitalAddressEdge, error) {
    wg := &sync.WaitGroup{}
    mutex := sync.Mutex{}
    res := make([]graph_raw.CapitalAddressEdge, 0)
    limit := make(chan bool, 5)
    timeout := int64(9 * 60)
    var err error
    for i := 0; i < len(addrs); i += 800 {

        wg.Add(1)
        start := i
        go func(start int) {
            defer wg.Done()
            limit <- true
            if _, ok := ctx.Get("request_canceled"); ok {
                slog.Infof(ctx, "request_canceled: %v", request)
                err = serror.CustomErr("查询取消", serror.CustomErrCode)
                return
            }
            cTime := time.Now().Unix()
            totalTime := cTime - sTime
            slog.Infof(ctx, "curr_time: %d, s_time: %d, total_time: %d, i: %d, level: %d", cTime, sTime, totalTime, start, level)
            if totalTime >= timeout {
                slog.Warnf(ctx, "smart超时: %v, curr_time: %d, s_time: %d, total_time: %d, i: %d, level: %d, req_level: %d, txnCnt : %d, rival: %d", request.Address, cTime, sTime, totalTime, start, level, request.Level, *txnCnt, *rival)
                err = serror.CustomErr("查询超时", serror.CustomErrCode)
                return
            }

            group := collections.SliceSubRange(addrs, start, 800)
            edgeList, _ := multiGetAddressData(ctx, group, request)
            <-limit
            mutex.Lock()
            defer mutex.Unlock()
            for _, v := range edgeList {
                *txnCnt += int64(v.Count)
            }
            *rival += int64(len(edgeList))
            slog.Infof(ctx, "req_level: %d, curr_level: %d, txnCnt : %d, rival: %d, stime: %d ", request.Level, level, *txnCnt, *rival, sTime)

            res = append(res, edgeList...)
        }(start)

        if err != nil {
            return res, err
        }
    }
    wg.Wait()

    return res, err
}

git merge --squash xxx

合并并将所有提交放到暂存区,需手动提交,作用:将多个提交合为1个。

go build 是 Go 语言的一个编译打包工具,它可以将 Go 语言编写的源码文件编译并打包成一个可执行文件。
在执行 go build 的过程中,Go 编译器会进行几个步骤的操作:
解析源代码,生成 AST(抽象语法树)。
从 AST 生成 SSA(静态单分配)形式的字节码。
优化字节码。
生成机器代码,生成可执行文件。
如果你想在 go build 执行过程中进行某些操作,你可能需要使用 Go 的工具链,如 go/ast,go/token,go/parser,go/types 等包来操作 AST,或者使用 go/ssa 包来操作 SSA 形式的字节码。

用channel控制协程数量

在 Golang 中,可以使用各种方法来控制并发并限制 goroutine 的数量。以下是一种可能的实现方式,可以最多同时运行 50 个 goroutine:

package main

import (
    "sync"
)

func main() {
    maxConcurrency := 50
    taskCount := 100

    var wg sync.WaitGroup
    semaphore := make(chan struct{}, maxConcurrency)

    for i := 0; i < taskCount; i++ {
        wg.Add(1)
        go func(taskID int) {
            semaphore <- struct{}{} // 占用一个信号量,限制并发数量

            // 执行你的任务代码
            // ...

            <-semaphore // 释放信号量
            wg.Done()
        }(i)
    }

    wg.Wait()
}

在上面的示例中,使用了一个 sync.WaitGroup 来等待所有任务完成。通过创建一个有容量的 chan struct{},我们可以使用带缓冲的通道作为信号量来控制 goroutine 的数量。maxConcurrency 变量定义了最大并发数量。

在每个 goroutine 中,首先会占用一个信号量(通过将空结构体写入通道),这将限制并发数量。然后在任务完成后释放信号量(通过从通道读取一个值)。sync.WaitGroup 用于等待所有任务完成。

请注意,这只是一种示例实现方式,您可以根据实际需求和情况进行适当的调整和修改。确保在使用并发控制时遵循最佳实践,以避免竞态条件和其他并发问题。

Q1: elasticsearch不能以root身份启动

Q2:

[2023-11-14T11:34:19,978][WARN ][o.e.h.n.Netty4HttpServerTransport] [safeis-qitaihe-test] received plaintext http traffic on an https channel, closing connection Netty4HttpChannel{localAddress=/127.0.0.1:9200, remoteAddress=/127.0.0.1:33384}

解决方法:

es/config/es.yml
xpack.security.enabled: true => xpack.security.enabled: false

require: 必传
oneof: "oneof=left right",其中一个。
min:验证数值类型或字符串类型的值是否大于等于指定的最小值;
max:验证数值类型或字符串类型的值是否小于等于指定的最大值;
eq:验证两个值是否相等;
ne:验证两个值是否不相等;
len:验证字符串、数组或切片的长度是否等于指定的长度;
regex:验证字符串是否匹配指定的正则表达式;
email:验证字符串是否为有效的电子邮件地址;
url:验证字符串是否为有效的 URL 地址;
numeric:验证字符串是否只包含数字字符;
alpha:验证字符串是否只包含字母字符;
alphanum:验证字符串是否只包含字母和数字字符;
ascii:验证字符串是否只包含 ASCII 字符;
base64:验证字符串是否为有效的 Base64 编码;
file:验证上传的文件是否符合要求,例如文件类型、大小等。