什么可以创造巨大的goroutines开销?

时间:2013-10-09 21:52:12

标签: go

对于我们正在使用的赋值,我们要做的其中一件事就是逐行解析uniprotdatabasefile以收集uniprot记录。

我不想共享太多代码,但是我有一个工作代码片段,可以在48秒内正确解析这样一个文件(2.5 GB)(使用时间go-package测量)。它迭代地解析文件并向记录添加行,直到达到记录结束信号(完整记录),并创建记录上的元数据。然后记录字符串为空,并逐行收集新记录。然后我想我会尝试使用常规例程。

我之前从stackoverflow获得了一些提示,然后到原始代码我简单地添加了一个函数来处理有关元数据创建的所有内容。

所以,代码正在进行

  1. 创建一个空记录,
  2. 迭代文件并在记录中添加行
  3. 如果找到记录停止信号(现在我们有完整的记录) - 将其设置为go例程以创建元数据
  4. 将记录字符串置空并从2)继续。
  5. 我还添加了sync.WaitGroup()以确保我等待(最后)完成每个例程。我认为这实际上会减少解析数据库文件所花费的时间,因为它继续解析,而goroutine会对每条记录起作用。但是,代码似乎运行超过20分钟,表明出现问题或开销变得疯狂。有什么建议吗?

    package main
    
    import (
        "bufio"
        "crypto/sha1"
        "fmt"
        "io"
        "log"
        "os"
        "strings"
        "sync"
        "time"
    )
    
    type producer struct {
        parser uniprot
    }
    
    type unit struct {
        tag string
    }
    
    type uniprot struct {
        filenames     []string
        recordUnits   chan unit
        recordStrings map[string]string
    }
    
    func main() {
        p := producer{parser: uniprot{}}
        p.parser.recordUnits = make(chan unit, 1000000)
        p.parser.recordStrings = make(map[string]string)
        p.parser.collectRecords(os.Args[1])
    }
    
    func (u *uniprot) collectRecords(name string) {
        fmt.Println("file to open ", name)
        t0 := time.Now()
        wg := new(sync.WaitGroup)
        record := []string{}
        file, err := os.Open(name)
        errorCheck(err)
        scanner := bufio.NewScanner(file)
        for scanner.Scan() { //Scan the file
            retText := scanner.Text()
            if strings.HasPrefix(retText, "//") {
                wg.Add(1)
                go u.handleRecord(record, wg)
                record = []string{}
            } else {
                record = append(record, retText)
            }
        }
        file.Close()
        wg.Wait()
        t1 := time.Now()
        fmt.Println(t1.Sub(t0))
    }
    
    func (u *uniprot) handleRecord(record []string, wg *sync.WaitGroup) {
        defer wg.Done()
        recString := strings.Join(record, "\n")
        t := hashfunc(recString)
        u.recordUnits <- unit{tag: t}
        u.recordStrings[t] = recString
    }
    
    func hashfunc(record string) (hashtag string) {
        hash := sha1.New()
        io.WriteString(hash, record)
        hashtag = string(hash.Sum(nil))
        return
    }
    
    func errorCheck(err error) {
        if err != nil {
            log.Fatal(err)
        }
    }
    

1 个答案:

答案 0 :(得分:3)

首先:您的代码不是线程安全的。主要是因为您正在访问哈希映射 同时。这些对于并发安全并不安全,需要锁定。代码中的错误行:

u.recordStrings[t] = recString

当你跑步时会爆炸,请使用GOMAXPROCS&gt; 1,我假设你没有这样做。确保使用GOMAXPROCS=2或更高版本运行应用程序以实现并行性。 默认值为1,因此您的代码在单个OS线程上运行,当然,这些线程无法同时在两个CPU或CPU内核上进行调度。例如:

$ GOMAXPROCS=2 go run udb.go uniprot_sprot_viruses.dat

最后:从频道中提取值,否则您的程序将不会终止。 如果goroutines的数量超过你的限制,你就会造成死锁。我用一个测试过 76MiB file of data,你说你的文件大约是2.5GB。我有16347个条目。假设线性增长, 您的文件将超过1e6,因此通道和程序中没有足够的插槽 将会死锁,在累积最终没有失败的goroutine时不会产生任何结果 (这次得分)。

所以解决方案应该是添加一个go例程,它从通道中提取值并执行 和他们在一起。

作为旁注:如果您担心性能,请不要使用字符串,因为它们总是被复制。请改用[]byte