分类 golang 下的文章

实例代码

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
}

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 用于等待所有任务完成。

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

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

这个报错是因为将 map[string]decimal.Decimal 定义为 decimal.Decimal 类型,而实际上在 map 中赋值的是未命名的浮点型数值 '0.0001'。Go 语言中,未命名的浮点数值默认是 float64 类型,而 decimal.Decimal 类型是不能直接将 float64 类型的值赋给它的。

要解决这个问题,可以将 map[string]decimal.Decimal 中的 float64 类型的值使用 decimal.NewFromFloat() 函数转换为 decimal.Decimal 类型的值。

例如:

var FilterJunkTrans = map[string]decimal.Decimal{

"USDT": decimal.NewFromFloat(1),
"DAI": decimal.NewFromFloat(1),
"USDC": decimal.NewFromFloat(1),
"TUSD": decimal.NewFromFloat(1),
"HUSD": decimal.NewFromFloat(1),
"BUSD": decimal.NewFromFloat(1),
"TRX": decimal.NewFromFloat(1),
"ETH": decimal.NewFromFloat(0.00001),
"BNB": decimal.NewFromFloat(0.0001),
"WBNB": decimal.NewFromFloat(0.0001),

}
这样就可以将 float64 类型的值转换为 decimal.Decimal 类型的值,解决报错问题。

比较运算
greaterThan

当在循环内开启 goroutine 时,一定要非常小心地处理循环变量,避免它们在 goroutine 之间发生竞争,这是常见的 Go 并发问题之一。在当前给出的代码中,开启 goroutine 时使用的 tmpChain 变量就是循环变量 chain 的一个副本,但是它的值只会在循环开始时被赋值,而不会在每次迭代时重新赋值。因此,在你的代码里,如果开启了一些长时间运行的 goroutine 并且在迭代 chainCurrencyAddrMap 之后执行了它们,它们将会看到那个过时的 tmpChain 变量,而不是它们应该看到的当前值。这将导致 goroutine 处理的信息混乱,导致错误。

为了修复这个问题,一种解决方法是将循环变量传递给 goroutine 的参数中,这将确保在 goroutine 中使用的始终是变量的当前值。可以通过将 tmpChain := chain 改为 使用函数参数传递来实现。修改后的代码如下:

func getAddrDistInfoPatch(c * gin.Context,

rawData *graph_raw.GraphRawData, 
chainCurrencyAddrMap map[string]map[string][]string,
column []string) (inFlowMap map[string]map[string]map[string]aModel.AddressInfo, err error) {

mu := &sync.Mutex{}
wg := &sync.WaitGroup{}

inFlowMap = make(map[string]map[string]map[string]aModel.AddressInfo)

for chain, currencyInfo := range chainCurrencyAddrMap {
    for currency, addrs := range currencyInfo {
        limit := make(chan bool, 50)
        for i := 0; i < len(addrs); i += 50 {
            start := i
            wg.Add(1)
            limit <- true
            go func(c string, cr string) {
                defer wg.Done()
                group := collections.SliceSubRange(addrs, start, 50)
                req := aModel.AddressInfoReq{
                    Direction: contract.LeftDirection,
                    Chain:     c,
                    Currency:  cr,
                    MinAmount: rawData.SessionInfo.MinAmount,
                    Column:    column,
                    Addr:      group,
                    BeginTime: rawData.SessionInfo.BeginTime,
                    EndTime:   rawData.SessionInfo.EndTime,
                }

                tmp := map[string]aModel.AddressInfo{}
                resp := safeis.Call(c, "address", "getDistCurrencyAddrInfo",
                    http.MethodPost, nil, req, nil)
                if resp.Code != serror.Success {
                    slog.Errorf(c, "call address/getDistCurrencyAddrInfo failed, resp:%+v", resp)
                    return
                }
                _ = resp.ToStruct(&tmp)
                <-limit

                mu.Lock()
                defer mu.Unlock()

                for k, v := range tmp {
                    if _, ok := inFlowMap[k]; !ok {
                        inFlowMap[k] = make(map[string]map[string]aModel.AddressInfo)
                    }
                    if _, ok := inFlowMap[k][chain]; !ok {
                        inFlowMap[k][chain] = make(map[string]aModel.AddressInfo)
                    }
                    inFlowMap[k][chain][currency] = v
                }
            }(chain, currency)
        }
    }
}

wg.Wait()
return inFlowMap, nil

}
在上述代码中,通过将 tmpChain 赋值替换为将 chain 和 currency 作为参数传递给匿名函数,解决了迭代过程中 goroutine 错误描述的问题。

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,将每个算法封装起来,并且使它们之间可以互相替换。该模式使得算法可以独立于使用它的客户端而变化。

下面是一个使用 Golang 实现策略模式的示例代码:

package main

import "fmt"

type PaymentStrategy interface {
    Pay(amount float64) error
}

type CreditCardStrategy struct{}

func (cc *CreditCardStrategy) Pay(amount float64) error {
    fmt.Printf("Paid %0.2f using Credit Card.\n", amount)
    return nil
}

type PayPalStrategy struct{}

func (pp *PayPalStrategy) Pay(amount float64) error {
    fmt.Printf("Paid %0.2f using PayPal.\n", amount)
    return nil
}

type PaymentContext struct {
    strategy PaymentStrategy
}

func (pc *PaymentContext) SetPaymentStrategy(strategy PaymentStrategy) {
    pc.strategy = strategy
}

func (pc *PaymentContext) MakePayment(amount float64) error {
    return pc.strategy.Pay(amount)
}

func main() {
    creditCardStrategy := &CreditCardStrategy{}
    payPalStrategy := &PayPalStrategy{}

    paymentContext := &PaymentContext{}

    // 使用信用卡支付
    paymentContext.SetPaymentStrategy(creditCardStrategy)
    paymentContext.MakePayment(100.00)

    // 使用 PayPal 支付
    paymentContext.SetPaymentStrategy(payPalStrategy)
    paymentContext.MakePayment(200.00)
}

在上面的代码中,我们定义了一个 PaymentStrategy 接口,该接口包含一个 Pay 方法,用于支付指定金额。接下来,我们定义了两个具体的支付策略类:CreditCardStrategy 和 PayPalStrategy,它们实现了 PaymentStrategy 接口的 Pay 方法。

我们还定义了一个 PaymentContext 结构体,该结构体包含一个 strategy 字段,用于存储当前的支付策略。PaymentContext 结构体还定义了两个方法:SetPaymentStrategy 用于设置支付策略,MakePayment 用于执行支付操作。

最后,在 main 函数中,我们创建了一个 CreditCardStrategy 和 PayPalStrategy 实例,并分别使用它们来进行支付。可以看到,在 MakePayment 方法中,我们只需要调用 strategy.Pay(amount) 来执行支付操作,而无需关心具体的支付策略是什么。这正是策略模式的优势所在,它将算法的实现细节与客户端代码隔离开来,使得客户端代码更加简洁和易于维护。

在 Go 语言中,协程是轻量级的用户态线程,也称为 goroutine,运行在操作系统的线程之上,是 Go 语言的核心特性之一。协程的调度是由 Go 语言运行时(runtime)实现的,下面是协程调度的基本流程:

当一个 goroutine 被创建时,它的执行是由主 goroutine 控制的。

如果该 goroutine 遇到了一个 I/O 操作、系统调用、时间延迟等需要阻塞的事件,则该 goroutine 会将自己从内部的队列中移除,直到该事件完成。

运行时系统会从运行队列中调度一个可运行的 goroutine 继续执行。

当一个 goroutine 执行完毕时,它也会自动从队列中退出。

如果主 goroutine 卡住了,运行时系统会尝试捕获 panic 以便程序能够正常退出。

当所有 goroutine 都结束并且 main 函数退出后,程序退出。

总的来说,Go 语言中的协程调度采用了 M:N 模型,即运行时系统会将多个 goroutine 映射到少量的操作系统线程上进行执行。这种方式有效地利用了多处理器系统的资源,并且可以轻松地处理大量的并发请求,提高了程序的执行效率和性能。

在普通的编程中,编写并发程序时需要特别注意并发安全性,以避免数据竞争和其他并发问题。在Go语言中,下面是一些可能会出现并发不安全问题的情况:

共享变量:如果多个 goroutine 对一个共享变量进行读写,而至少有一个 goroutine 执行写操作时,就有可能会出现并发问题。为了避免这种情况,可以使用 sync 包或通道来同步访问共享变量。

非原子操作:如果一个变量的更新操作不是原子的,就可能会导致并发问题。例如对于 32 位的整数类型,在一个 goroutine 更新该变量的高 16 位,而在另一个 goroutine 更新低 16 位时,就有可能出现错误结果。为了避免这种情况,可以使用原子操作进行变量更新。

无序写入:如果多个 goroutine 向同一个切片(slice)同时写入数据,而没有采取任何措施来同步访问该切片,就有可能出现并发问题。为了避免这种情况,可以使用 sync 包或通道来同步访问共享数据。

共享 map:如果多个 goroutine 对同一个 map 进行读写操作,而至少有一个 goroutine 执行写操作时,就可能会出现并发问题。为了避免这种情况,可以使用 sync 包或者采用只读方式访问 map。

总的来说,Go语言具有强大的并发性能,而并发不安全问题主要是由于多个 goroutine 访问共享数据而引起的。开发人员在编写并发代码的同时,应该采取一些正确地同步机制,保证每个共享变量的访问被合理的同步以避免并发不安全问题。

func (db *DB) Unscoped() (tx *DB) {
    tx = db.getInstance()
    tx.Statement.Unscoped = true
    return
}

Unscoped办法设置tx.Statement.Unscoped为true

gorm的Select、Delete方法都是软删除,Select会自动筛选delete_at为空的记录,Delete会更新delete_at。

如果需要获取已删除的记录,可以tx.Unscoped().Select()。
如果需要物理删除,可以tx.Unscoped().Delete().