How do you sync multiple goroutines logging to stdout/file?

agolangf · · 796 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>Hello, go newbie here. I&#39;ve got a program that uses several (20+) goroutines to handle fetching data, processing, and handling. Each routine is for{}. This all works great. The problem is reading the stdout.</p> <p>Right now, every routine just does log.Printf/Println with a prefix string so I can identify each by name. As you can imagine, all of the lines are intermixed. I may see ABA, BCA, ABC. What I want is to see AAA,BBB,CCC. Essentially, buffering/locking the output and only printing it to stdout when that iteration of that routine&#39;s loop is done.</p> <p>Is this buffering the correct approach over using sync.RWMutex? Does anyone have any sample code on this implementation?</p> <p>Thanks!</p> <hr/>**评论:**<br/><br/>sethammons: <pre><p>You could have them all share a channel and they could write to the channel. A single for-select loop or range over the channel to pull out output is an option. You could pass through a struct of {name string; log string} and collect the the output however you like. Or a buffer as you proposed. Lots of options. </p></pre>utdrmac: <pre><p>I like this, but still seems like it would still be random/interwoven output. Would I have to add some sort of &#34;flush&#34; method/action through the channel to inform the logger that it can now print all of that particular &#39;name&#39;s buffered up contents?</p></pre>sethammons: <pre><p>Could do that. It feels a bit more over-engineered than just having buffers in each goroutine. But, yeah, you could have the struct passed on the channel have a flush bool attribute. </p></pre>qu33ksilver: <pre><p>No need to use a mutex. Send the log lines over a channel and start appending them to a <code>map[prefix][]string</code>. Have a ticker running, which at certain intervals flush the map by writing the values to stdout/file.</p> <pre><code>ticker := time.NewTicker(flushIntervalSecs * time.Second) for { select { case &lt;-ticker.C: // flush the map, write to target case logLine := &lt;-logChan: // add to map } } </code></pre> <p>The writes and reads on the map are automatically serialized because of the <code>for-select</code> ! You can make the channel buffered if you don&#39;t want your application to block during the flush is going on. But then while reading from the map, you have to do some juggling around.</p></pre>allhatenocattle: <pre><p>I think having each goroutine write to a *bytes.Buffer and then logging at the end would be the best way to group the lines together. One downside is that if a routine is stuck in the middle of something you wouldn&#39;t see any output to help debug it.</p></pre>utdrmac: <pre><p>Thanks. This was my original thought as well. Just wondering if there was &#34;a better way.&#34; Have you seen any existing libraries/code that does this so I could take a look?</p></pre>allhatenocattle: <pre><p>Here is an example:</p> <pre><code>package main import ( &#34;bytes&#34; &#34;fmt&#34; &#34;strconv&#34; &#34;sync&#34; ) func yourfunc(message string, w *sync.WaitGroup) { defer w.Done() b := &amp;bytes.Buffer{} defer fmt.Print(b) fmt.Fprintf(b, &#34;starting yourfunc with %s\n&#34;, message) fmt.Fprintf(b, &#34;message is %s\n&#34;, message) fmt.Fprintf(b, &#34;finished yourfunc with %s\n&#34;, message) } func main() { w := &amp;sync.WaitGroup{} messages := make([]string, 0) for i := 0; i &lt; 100; i++ { messages = append(messages, strconv.Itoa(i)) } for _, m := range messages { w.Add(1) go yourfunc(m, w) } w.Wait() } </code></pre> <p>When I run that on my machine, the goroutine finish order is kinda random, but all the lines are together:<br/> ...<br/> starting yourfunc with 57 message is 57<br/> finished yourfunc with 57<br/> starting yourfunc with 77<br/> message is 77<br/> finished yourfunc with 77<br/> starting yourfunc with 78<br/> message is 78<br/> finished yourfunc with 78<br/> starting yourfunc with 72<br/> message is 72<br/> finished yourfunc with 72<br/> starting yourfunc with 79<br/> message is 79<br/> finished yourfunc with 79<br/> starting yourfunc with 73<br/> message is 73<br/> finished yourfunc with 73<br/> ... </p> <p>[edit: formatting]</p></pre>Femaref: <pre><p>yes, buffering is the best choice here. if you were using a mutex the only way to see the logs grouped together would be to hold the lock until the iteration is over. If that&#39;s the case, your application essentially would have a single go routine. Save the output of each goroutine into a buffer, and write it to the log at the end of the loop.</p></pre>NeoinKarasuYuu: <pre><p>I like <a href="/u/qu33ksilver" rel="nofollow">/u/qu33ksilver</a>&#39;s solution if this is needed for a quick command line application. </p> <p>But if this is for a server, a slightly more intricate solution might be nice. For a similar usecase (although not strictly logging) I had, I dumped the data into boltDB and exposed the data over grpc API. Every goroutine&#39;s logs goes into a different bucket in boltDB, using the prefix as the key for that bucket and the autogen id as the key for a entry.<br/> On grpc there are two endpoints. One endpoint to query a list of goroutines, and another one to get the log of one goroutine. Note, with grpc you can stream the new log entries to clients. A client can then be written in any front-end that can do grpc.</p> <p>The advantage of boltDB+grpc is that multiple devs can connect to to the server and inspect the logs (and search them based on prefix). </p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

796 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传