<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fahim Faisaal</title>
    <description>The latest articles on DEV Community by Fahim Faisaal (@fahimfaisaal).</description>
    <link>https://dev.to/fahimfaisaal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2905947%2Fd228da39-9e24-4d24-8022-7e4d3dfffe15.png</url>
      <title>DEV Community: Fahim Faisaal</title>
      <link>https://dev.to/fahimfaisaal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fahimfaisaal"/>
    <language>en</language>
    <item>
      <title>How VarMQ Processes Millions of Messages with an Event Loop and Worker Pool</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Sat, 01 Aug 2026 01:31:56 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/how-varmq-processes-millions-of-messages-with-an-event-loop-and-worker-pool-3i6n</link>
      <guid>https://dev.to/fahimfaisaal/how-varmq-processes-millions-of-messages-with-an-event-loop-and-worker-pool-3i6n</guid>
      <description>&lt;p&gt;Processing a million messages is easy if time and memory don't matter. The hard part is doing it without paying a big dispatch cost per message, without letting concurrency run wild, and without spinning up a fresh goroutine or a fresh allocation every time something arrives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt; tackles that with two pieces that cooperate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One event loop decides when the next job may start.&lt;/li&gt;
&lt;li&gt;A reusable worker pool runs jobs concurrently, up to a limit you configure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The queue sits between the producers and these two pieces. Producers can add work as fast as they want, but the event loop controls how quickly that work reaches the pool. That separation keeps the submission path cheap, and it means a burst of messages never becomes an equally large burst of goroutines.&lt;/p&gt;

&lt;h2&gt;
  
  
  The processing path
&lt;/h2&gt;

&lt;p&gt;Here's what a message goes through inside an in-memory VarMQ worker:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpe24kqdz2saho70mre95.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpe24kqdz2saho70mre95.png" alt=" " width="799" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Adding a message never creates a goroutine. It builds a job, enqueues it, and sends a wake-up notification to the worker. Only the event loop can admit queued jobs into execution inside the worker pool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The event loop dispatches; it does not execute
&lt;/h2&gt;

&lt;p&gt;The core loop is small enough to understand in one pass. This is a shortened version of the event loop&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="c"&gt;// Wait for in-flight work, clean up the pool, and stop.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IsActive&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;curProcessing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrency&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queues&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;cont&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processNextJob&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sendError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;cont&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When there's nothing to do, the loop just blocks in &lt;code&gt;select&lt;/code&gt;. It doesn't poll the queue, and it doesn't burn a CPU core while idle.&lt;/p&gt;

&lt;p&gt;Once signaled, it keeps dispatching until one of three conditions changes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The worker is paused or stopping.&lt;/li&gt;
&lt;li&gt;The configured concurrency limit has been reached.&lt;/li&gt;
&lt;li&gt;Every bound queue is empty.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So a single wake-up can dispatch many jobs. There's no one-to-one relationship between messages and event-loop wake-ups.&lt;/p&gt;

&lt;p&gt;The signal channel has a capacity of one, and notifications use a non-blocking send:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;eventLoopSignal&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If 10,000 messages arrive while a wake-up is already waiting, the extra notifications collapse into the pending signal. The event loop wakes up, inspects the real queue length, and fills every available processing slot. That's how a burst of submissions avoids becoming thousands of blocked signal senders.&lt;/p&gt;

&lt;p&gt;Workers send the same notification when they finish. Releasing a processing slot decrements the in-flight counter and wakes the loop if more capacity is available. The result is a short feedback cycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;message arrives -&amp;gt; event loop fills pool -&amp;gt; worker finishes -&amp;gt; slot released -&amp;gt; event loop fills slot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The pool bounds concurrency
&lt;/h2&gt;

&lt;p&gt;The event loop dispatches serially, but handlers run &lt;strong&gt;concurrently&lt;/strong&gt; inside pool nodes. Each node owns a small channel and a goroutine that serves it. When the event loop dispatches a job, it removes an idle node from the pool and sends the job to it.&lt;/p&gt;

&lt;p&gt;If no idle node exists, VarMQ creates another one. The event loop's &lt;code&gt;curProcessing &amp;lt; concurrency&lt;/code&gt; check keeps this from running past the configured limit. A queue may hold a million messages, but a worker configured with concurrency 32 will execute at most 32 at a time.&lt;/p&gt;

&lt;p&gt;When a handler returns, its node follows one of two paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It goes back into the idle list when more work is waiting or the configured idle-worker policy wants to keep it warm.&lt;/li&gt;
&lt;li&gt;It stops, and its linked-list node goes into a &lt;code&gt;sync.Pool&lt;/code&gt; cache for later reuse.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can tune concurrency while the worker is running. Increasing it wakes the event loop immediately. Decreasing it changes admission for new jobs and lets excess idle workers disappear without interrupting jobs already in flight.&lt;/p&gt;

&lt;p&gt;This is where VarMQ's concurrency comes from. The event loop does a small amount of coordination; the pool spends its time inside application handlers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the memory savings come from
&lt;/h2&gt;

&lt;p&gt;"Low memory usage" here means low overhead per message and bounded execution resources. It does not mean an unlimited backlog somehow consumes constant memory.&lt;/p&gt;

&lt;p&gt;VarMQ trims overhead in a few places.&lt;/p&gt;

&lt;h3&gt;
  
  
  Chunked queue storage (Default standard Queue)
&lt;/h3&gt;

&lt;p&gt;The standard FIFO queue stores jobs in linked buffer chunks instead of repeatedly growing and copying one large slice or a linked list node for each message. It starts with space for 1,024 entries. When a chunk fills, the next chunk grows by 50 percent, up to the configured maximum chunk size.&lt;/p&gt;

&lt;p&gt;Enqueue and dequeue remain amortized O(1). Crossing a chunk boundary advances a pointer rather than copying unread messages into a new array. A dequeue also clears the consumed slot, so the queue doesn't keep a reference to a completed job and its payload.&lt;/p&gt;

&lt;p&gt;The payoff: no large copy spikes when a single backing array grows under a heavy backlog.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reused worker nodes
&lt;/h3&gt;

&lt;p&gt;Pool nodes move between the busy state, the idle linked list, and a &lt;code&gt;sync.Pool&lt;/code&gt; cache. Reusing those nodes means the bookkeeping around a worker doesn't have to be rebuilt every time traffic rises after an idle period.&lt;/p&gt;

&lt;p&gt;The optional idle expiry policy also lets an application pick its memory and latency tradeoff. Keeping idle workers warm reduces dispatch latency. Expiring excess workers reduces retained pool state during quiet periods. VarMQ always keeps at least the configured minimum idle ratio, with one idle worker as the default minimum.&lt;/p&gt;

&lt;h3&gt;
  
  
  Small coordination state
&lt;/h3&gt;

&lt;p&gt;The worker tracks its status, concurrency, and current processing count with atomics. Locks still protect queue chunks, pool lists, and condition-variable transitions, so the design is not lock-free. But the hot checks in the event loop don't need a large shared scheduler object or a goroutine for every queued message.&lt;/p&gt;

&lt;p&gt;VarMQ also offers different job types for different needs. A fire-and-forget &lt;code&gt;Worker&lt;/code&gt; carries less result state than &lt;code&gt;ErrWorker&lt;/code&gt; or &lt;code&gt;ResultWorker&lt;/code&gt;. Choosing the smallest useful abstraction shows up directly in the allocation numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  VarMQ under load: a head-to-head benchmark against PondV2
&lt;/h2&gt;

&lt;p&gt;The repository's own microbenchmark - a trivial handler, one CPU, concurrency one - measures VarMQ's scheduler overhead in isolation (roughly 1.01 million round trips per second for single adds, 1.54 million for batches). To see how the design behaves under realistic load - many producers, different handler shapes, and different CPU counts - we ran a head-to-head comparison against &lt;a href="https://github.com/alitto/pond" rel="noopener noreferrer"&gt;PondV2&lt;/a&gt;, a popular Go worker pool with a feature set close to VarMQ's. VarMQ's design was partly inspired by PondV2, which makes the comparison fair and informative.&lt;/p&gt;

&lt;p&gt;The full benchmark suite lives in the &lt;a href="https://github.com/goptics/varmq-benchmarks" rel="noopener noreferrer"&gt;varmq-benchmarks repository&lt;/a&gt;. Interactive charts for every result, grouped by workload and CPU configuration, are published at &lt;a href="https://goptics.github.io/varmq-benchmarks/" rel="noopener noreferrer"&gt;goptics.github.io/varmq-benchmarks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Each run exercises the complete path from submitting tasks to waiting for completion. The suite covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;three task shapes - &lt;code&gt;Sleep10ms&lt;/code&gt; (I/O simulation), &lt;code&gt;Loop10&lt;/code&gt; (CPU-bound math), and &lt;code&gt;Sleep5Loop5&lt;/code&gt; (mixed);&lt;/li&gt;
&lt;li&gt;five producer patterns - from 1 user submitting 1M tasks (&lt;code&gt;1u-1Mt&lt;/code&gt;) to 1M users submitting 1 task each (&lt;code&gt;1Mu-1t&lt;/code&gt;);&lt;/li&gt;
&lt;li&gt;pool sizes of 50k, 100k, and 300k workers;&lt;/li&gt;
&lt;li&gt;three CPU configurations - 4, 8, and 24 (the i7-13700's default &lt;code&gt;GOMAXPROCS&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frkir7zb9cu96iixjfbvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frkir7zb9cu96iixjfbvb.png" alt=" " width="800" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All runs used the same machine as the microbenchmark: an &lt;code&gt;Intel Core i7-13700 on Linux/amd64&lt;/code&gt;. The tables below show representative results; the charts hold the full set.&lt;/p&gt;

&lt;h3&gt;
  
  
  4 CPUs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;VarMQ&lt;/th&gt;
&lt;th&gt;PondV2&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Memory (VarMQ / Pond)&lt;/th&gt;
&lt;th&gt;Allocations (VarMQ / Pond)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Sleep10ms&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,414,893 ops/s&lt;/td&gt;
&lt;td&gt;639,325 ops/s&lt;/td&gt;
&lt;td&gt;2.2x&lt;/td&gt;
&lt;td&gt;150 MB / 365 MB&lt;/td&gt;
&lt;td&gt;2.38 M / 8.23 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loop10&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,894,515 ops/s&lt;/td&gt;
&lt;td&gt;831,844 ops/s&lt;/td&gt;
&lt;td&gt;2.3x&lt;/td&gt;
&lt;td&gt;117 MB / 241 MB&lt;/td&gt;
&lt;td&gt;2.09 M / 7.00 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loop10&lt;/code&gt;, 300k workers, &lt;code&gt;1Mu-1t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,532,306 ops/s&lt;/td&gt;
&lt;td&gt;472,661 ops/s&lt;/td&gt;
&lt;td&gt;3.2x&lt;/td&gt;
&lt;td&gt;165 MB / 333 MB&lt;/td&gt;
&lt;td&gt;3.60 M / 8.48 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Sleep5Loop5&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,581,041 ops/s&lt;/td&gt;
&lt;td&gt;689,866 ops/s&lt;/td&gt;
&lt;td&gt;2.3x&lt;/td&gt;
&lt;td&gt;127 MB / 340 MB&lt;/td&gt;
&lt;td&gt;2.22 M / 8.02 M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  8 CPUs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;VarMQ&lt;/th&gt;
&lt;th&gt;PondV2&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Memory (VarMQ / Pond)&lt;/th&gt;
&lt;th&gt;Allocations (VarMQ / Pond)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Sleep10ms&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,398,627 ops/s&lt;/td&gt;
&lt;td&gt;684,068 ops/s&lt;/td&gt;
&lt;td&gt;2.0x&lt;/td&gt;
&lt;td&gt;148 MB / 368 MB&lt;/td&gt;
&lt;td&gt;2.37 M / 8.26 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loop10&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,710,220 ops/s&lt;/td&gt;
&lt;td&gt;891,813 ops/s&lt;/td&gt;
&lt;td&gt;1.9x&lt;/td&gt;
&lt;td&gt;117 MB / 241 MB&lt;/td&gt;
&lt;td&gt;2.10 M / 7.00 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loop10&lt;/code&gt;, 100k workers, &lt;code&gt;1Mu-1t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,361,620 ops/s&lt;/td&gt;
&lt;td&gt;219,070 ops/s&lt;/td&gt;
&lt;td&gt;6.2x&lt;/td&gt;
&lt;td&gt;152 MB / 345 MB&lt;/td&gt;
&lt;td&gt;3.18 M / 8.18 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Sleep10ms&lt;/code&gt;, 300k workers, &lt;code&gt;1Mu-1t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1,110,899 ops/s&lt;/td&gt;
&lt;td&gt;341,835 ops/s&lt;/td&gt;
&lt;td&gt;3.3x&lt;/td&gt;
&lt;td&gt;202 MB / 482 MB&lt;/td&gt;
&lt;td&gt;3.99 M / 9.28 M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  24 CPUs
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload&lt;/th&gt;
&lt;th&gt;VarMQ&lt;/th&gt;
&lt;th&gt;PondV2&lt;/th&gt;
&lt;th&gt;Speedup&lt;/th&gt;
&lt;th&gt;Memory (VarMQ / Pond)&lt;/th&gt;
&lt;th&gt;Allocations (VarMQ / Pond)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Sleep10ms&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;871,903 ops/s&lt;/td&gt;
&lt;td&gt;357,154 ops/s&lt;/td&gt;
&lt;td&gt;2.4x&lt;/td&gt;
&lt;td&gt;150 MB / 361 MB&lt;/td&gt;
&lt;td&gt;2.38 M / 8.19 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loop10&lt;/code&gt;, 50k workers, &lt;code&gt;1u-1Mt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;936,156 ops/s&lt;/td&gt;
&lt;td&gt;226,394 ops/s&lt;/td&gt;
&lt;td&gt;4.1x&lt;/td&gt;
&lt;td&gt;116 MB / 240 MB&lt;/td&gt;
&lt;td&gt;2.08 M / 7.00 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Loop10&lt;/code&gt;, 300k workers, &lt;code&gt;1Mu-1t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;671,072 ops/s&lt;/td&gt;
&lt;td&gt;158,539 ops/s&lt;/td&gt;
&lt;td&gt;4.2x&lt;/td&gt;
&lt;td&gt;160 MB / 367 MB&lt;/td&gt;
&lt;td&gt;3.44 M / 8.72 M&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;Sleep5Loop5&lt;/code&gt;, 50k workers, &lt;code&gt;1Mu-1t&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;577,518 ops/s&lt;/td&gt;
&lt;td&gt;593,402 ops/s&lt;/td&gt;
&lt;td&gt;~1.0x&lt;/td&gt;
&lt;td&gt;160 MB / 349 MB&lt;/td&gt;
&lt;td&gt;3.22 M / 7.40 M&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What the numbers show:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VarMQ won every workload at every CPU count. The typical throughput advantage is roughly 2–2.5x, and the widest gap is 6.2x - the CPU-bound &lt;code&gt;Loop10&lt;/code&gt; workload with one million producers at 8 CPUs.&lt;/li&gt;
&lt;li&gt;Memory use stays about 2.4–2.5x lower and allocations about 3.4–3.5x lower regardless of CPU count, which matches the queue chunking and node reuse described above.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;1Mu-1t&lt;/code&gt; pattern many producers, one task each - is where PondV2 degrades most. That is exactly the case VarMQ's design targets: &lt;code&gt;Add&lt;/code&gt; never creates a goroutine, and producer wake-ups coalesce into one event-loop signal.&lt;/li&gt;
&lt;li&gt;Both libraries slow down at 24 CPUs - the i7-13700 has 16 physical cores and 24 threads, so hyper-threading adds scheduling pressure - but VarMQ keeps its lead.&lt;/li&gt;
&lt;li&gt;The one near-parity result is &lt;code&gt;Sleep5Loop5&lt;/code&gt; at 24 CPUs with a million producers, where the two libraries land within a few percent of each other. Everything else is a clear VarMQ win.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As with the microbenchmark, these are derived rates from &lt;code&gt;ns/op&lt;/code&gt; on one machine, with the benchmark harness overhead included. Throughput on your hardware and handler shape will differ. The charts let you compare any workload across all three CPU configurations, and the repository documents how to re-run the suite yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backlogs still need a memory budget
&lt;/h2&gt;

&lt;p&gt;Bounded concurrency protects execution, not queue length. If producers submit work faster than handlers complete it, pending messages accumulate - and every pending job and its payload has to live somewhere.&lt;/p&gt;

&lt;p&gt;For a service that may receive sustained bursts, set a queue capacity and decide what the producer should do when &lt;code&gt;Add&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;. Depending on the application, that may mean retrying with backoff, shedding low-priority work, writing to a durable queue, or applying backpressure earlier in the request path.&lt;/p&gt;

&lt;p&gt;That's how low per-message overhead turns into predictable memory usage. Without a capacity, even an efficient queue can exhaust memory when the arrival rate stays above the completion rate long enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Graceful shutdown keeps concurrency manageable
&lt;/h2&gt;

&lt;p&gt;The same event loop owns shutdown coordination. Cancelling the worker context moves the worker to &lt;code&gt;stopping&lt;/code&gt;, waits for in-flight jobs, removes pool workers, broadcasts the final state, and exits the event-loop goroutine.&lt;/p&gt;

&lt;p&gt;Pause works differently. It stops admission of new jobs while allowing current handlers to finish. Resume returns the worker to &lt;code&gt;idle&lt;/code&gt; and signals the event loop so queued work starts again. These state transitions let an application control traffic without abandoning jobs that are already running.&lt;/p&gt;

&lt;h2&gt;
  
  
  The useful idea is separation
&lt;/h2&gt;

&lt;p&gt;VarMQ gets high throughput by keeping responsibilities narrow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Producers enqueue and send a cheap, coalesced notification.&lt;/li&gt;
&lt;li&gt;The event loop admits work only while capacity exists.&lt;/li&gt;
&lt;li&gt;Pool workers run handlers concurrently and report completion.&lt;/li&gt;
&lt;li&gt;Chunked queues and reusable pool nodes keep allocation pressure under control.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The event loop doesn't make handlers faster; it makes dispatch predictable. The pool doesn't make the queue smaller; it keeps active concurrency bounded. Together, they let &lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt; process large message volumes without creating execution resources in proportion to the backlog.&lt;/p&gt;

&lt;p&gt;That is how a small Go scheduler can move more than a million trivial messages per second in a microbenchmark while still giving production applications explicit control over concurrency, memory, and shutdown behavior.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>eventdriven</category>
      <category>go</category>
    </item>
    <item>
      <title>Visualize Your Entire GitHub Contribution History as a Stunning 3D Skyline — With Just One Command</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Fri, 24 Jul 2026 14:28:41 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/visualize-your-entire-github-contribution-history-as-a-stunning-3d-skyline-with-just-one-command-fpl</link>
      <guid>https://dev.to/fahimfaisaal/visualize-your-entire-github-contribution-history-as-a-stunning-3d-skyline-with-just-one-command-fpl</guid>
      <description>&lt;p&gt;Have you ever wanted to see your full GitHub activity — every contribution across years — as an interactive 3D bar chart? With &lt;a href="https://github.com/goptics/vizb" rel="noopener noreferrer"&gt;Vizb&lt;/a&gt;, you can turn the entire contribution history of any GitHub user into a beautiful, self-contained 3D “skyline” in a single terminal command.&lt;/p&gt;

&lt;p&gt;No complex setup. No external servers. Just one pipeline that fetches the data and generates a fully interactive HTML visualization.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Vizb?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/goptics/vizb" rel="noopener noreferrer"&gt;Vizb&lt;/a&gt; is a lightweight CLI tool that transforms tabular data (CSV, JSON, or benchmark output) into interactive charts. It supports multi-dimensional grouping, so dates can be split into &lt;code&gt;Year / Month / Day&lt;/code&gt; axes — perfect for turning contribution calendars into 3D skyline charts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Install
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Linux / MacOS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://vizb.goptics.org/install.sh | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windows Powershell
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;irm https://vizb.goptics.org/install.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Magic Command
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://github-contributions-api.jogruber.de/v4/&amp;lt;username&amp;gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | vizb bar &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--group-pattern&lt;/span&gt; &lt;span class="s1"&gt;'[z{Year}-y{Month}-x{Date}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--json-path&lt;/span&gt; &lt;span class="s1"&gt;'.contributions'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--select&lt;/span&gt; &lt;span class="s1"&gt;'count{Contributions}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--output&lt;/span&gt; index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example: Linus Torvalds&lt;/p&gt;

&lt;p&gt;Let’s use the creator of Linux and Git himself. Torvalds has made more than 37,000 contributions across roughly 16 years on GitHub.&lt;/p&gt;

&lt;p&gt;Replace &lt;code&gt;&amp;lt;username&amp;gt;&lt;/code&gt; with &lt;code&gt;torvalds&lt;/code&gt;:&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux / MacOS
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"https://github-contributions-api.jogruber.de/v4/torvalds"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | vizb bar &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--group&lt;/span&gt; &lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--group-pattern&lt;/span&gt; &lt;span class="s1"&gt;'[z{Year}-y{Month}-x{Date}]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--json-path&lt;/span&gt; &lt;span class="s1"&gt;'.contributions'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--select&lt;/span&gt; &lt;span class="s1"&gt;'count{Contributions}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--stat&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
      &lt;span class="nt"&gt;--output&lt;/span&gt; index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Windows Powershell
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(iwr "https://github-contributions-api.jogruber.de/v4/torvalds").Content `
  | vizb bar `
      --group date `
      --group-pattern '[z{Year}-y{Month}-x{Date}]' `
      --json-path '.contributions' `
      --select 'count{Contributions}' `
      --stat `
      --output index.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the generated &lt;code&gt;index.html&lt;/code&gt; in any browser. You’ll get an interactive 3D skyline like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprzn8yhx1mqxdkwftq3r.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fprzn8yhx1mqxdkwftq3r.gif" alt=" " width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each year becomes a new layer (z-axis)&lt;/li&gt;
&lt;li&gt;Months form the depth (y-axis)&lt;/li&gt;
&lt;li&gt;Individual days become the bars (x-axis)&lt;/li&gt;
&lt;li&gt;Bar height = contribution count that day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can rotate, zoom, hover for exact numbers, toggle the statistics panel, and even export the view.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Works So Well
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The public GitHub Contributions API returns every day’s count in clean JSON&lt;/li&gt;
&lt;li&gt;Vizb’s smart grouping pattern &lt;code&gt;[z{Year}-y{Month}-x{Date}]&lt;/code&gt; automatically builds the 3D axes&lt;/li&gt;
&lt;li&gt;The entire visualization is a single self-contained HTML file — no dependencies, no complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Try it with your own username (or any public profile). In under 10 seconds you’ll have a personal 3D contribution skyline you can share, embed, or just admire.&lt;/p&gt;

&lt;p&gt;One command. Your entire GitHub story — visualized.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>data</category>
    </item>
    <item>
      <title>How to Verify Your Git Commits and Tags with GPG: A Step-by-Step Guide</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Mon, 29 Dec 2025 05:37:50 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/how-to-verify-your-git-commits-with-gpg-a-step-by-step-guide-3f86</link>
      <guid>https://dev.to/fahimfaisaal/how-to-verify-your-git-commits-with-gpg-a-step-by-step-guide-3f86</guid>
      <description>&lt;p&gt;In the world of open source and collaborative development, identity is everything. When you see a commit from &lt;code&gt;torvalds&lt;/code&gt; on the Linux kernel, how do you know it's &lt;em&gt;actually&lt;/em&gt; Linus Torvalds and not an impersonator?&lt;/p&gt;

&lt;p&gt;The answer is GPG signing.&lt;/p&gt;

&lt;p&gt;By signing your commits with a GPG (GNU Privacy Guard) key, you cryptographically verify that the code came from you and hasn't been altered. GitHub (and GitLab) rewards this with a shiny green &lt;strong&gt;"Verified"&lt;/strong&gt; badge.&lt;/p&gt;

&lt;p&gt;Here is a straightforward guide to setting this up.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Install GPG
&lt;/h2&gt;

&lt;p&gt;First, you need the GPG tool installed on your machine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;macOS (Homebrew):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;gnupg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Linux (Debian/Ubuntu/Pop!_OS):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;gnupg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Windows:&lt;/strong&gt;&lt;br&gt;
Download and install &lt;a href="https://www.gpg4win.org/" rel="noopener noreferrer"&gt;Gpg4win&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Generate a GPG Key
&lt;/h2&gt;

&lt;p&gt;Run the following command to generate a new key pair.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--batch&lt;/span&gt; &lt;span class="nt"&gt;--passphrase&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="nt"&gt;--quick-gen-key&lt;/span&gt; &lt;span class="s2"&gt;"Your Name &amp;lt;your_email@example.com&amp;gt;"&lt;/span&gt; rsa4096 default never
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  3. Get Your Key ID
&lt;/h2&gt;

&lt;p&gt;Once generated, list your keys to find the Key ID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--list-secret-keys&lt;/span&gt; &lt;span class="nt"&gt;--keyid-format&lt;/span&gt; LONG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sec   rsa4096/3AA5C34371567BD2 2024-01-01 [SC]
      ...
uid                 [ultimate] John Doe &amp;lt;john@example.com&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, &lt;strong&gt;&lt;code&gt;3AA5C34371567BD2&lt;/code&gt;&lt;/strong&gt; is your Key ID.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Tell Git About Your Key
&lt;/h2&gt;

&lt;p&gt;Now configure Git to use this key for signing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set the key:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; user.signingkey 3AA5C34371567BD2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(Replace &lt;code&gt;3AA5C34371567BD2&lt;/code&gt; with your actual Key ID)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enable automatic signing (Optional but recommended):&lt;/strong&gt;&lt;br&gt;
This ensures every commit you make is signed by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config &lt;span class="nt"&gt;--global&lt;/span&gt; commit.gpgsign &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Add Your Public Key to GitHub
&lt;/h2&gt;

&lt;p&gt;Git knows about your key, but GitHub doesn't yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Export your public key:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gpg &lt;span class="nt"&gt;--armor&lt;/span&gt; &lt;span class="nt"&gt;--export&lt;/span&gt; 3AA5C34371567BD2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Copy the output:&lt;/strong&gt;&lt;br&gt;
Copy the entire block (including &lt;code&gt;-----BEGIN PGP PUBLIC KEY BLOCK-----&lt;/code&gt; and &lt;code&gt;-----END...&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add to GitHub:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to &lt;strong&gt;Settings&lt;/strong&gt; &amp;gt; &lt;strong&gt;SSH and GPG keys&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Click &lt;strong&gt;New GPG key&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Paste your key and save.&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  6. Verify It Works
&lt;/h2&gt;

&lt;p&gt;Make a commit in any repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"My first signed commit"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push it to GitHub. You should now see the verified badge next to your commit in the history!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw28ym06s2hhr7gxvdjm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvw28ym06s2hhr7gxvdjm.png" alt="Verified Badge Example" width="384" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;If you see "Unverified":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Check that the email in &lt;code&gt;git config user.email&lt;/code&gt; matches the email in your GPG key.&lt;/li&gt;
&lt;li&gt; Ensure that exact email is added and verified in your GitHub account settings.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>git</category>
      <category>gpg</category>
      <category>signature</category>
    </item>
    <item>
      <title>Try vzib to visualize the go ugly benchmarks to interactive charts</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Tue, 24 Jun 2025 15:19:55 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/try-vzib-to-visualize-the-go-ugly-benchmarks-to-interactive-charts-fp7</link>
      <guid>https://dev.to/fahimfaisaal/try-vzib-to-visualize-the-go-ugly-benchmarks-to-interactive-charts-fp7</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c" class="crayons-story__hidden-navigation-link"&gt;Vizb: Beautiful Go Benchmark Visualization Made Simple&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/fahimfaisaal" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2905947%2Fd228da39-9e24-4d24-8022-7e4d3dfffe15.png" alt="fahimfaisaal profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/fahimfaisaal" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Fahim Faisaal
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Fahim Faisaal
                
              
              &lt;div id="story-author-preview-content-2621293" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/fahimfaisaal" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2905947%2Fd228da39-9e24-4d24-8022-7e4d3dfffe15.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Fahim Faisaal&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 24 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c" id="article-link-2621293"&gt;
          Vizb: Beautiful Go Benchmark Visualization Made Simple
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/showdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;showdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/webdev"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;webdev&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/go"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;go&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cli"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cli&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;3&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>go</category>
      <category>cli</category>
    </item>
    <item>
      <title>Vizb: Beautiful Go Benchmark Visualization Made Simple</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Tue, 24 Jun 2025 14:32:37 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c</link>
      <guid>https://dev.to/fahimfaisaal/vizb-beautiful-go-benchmark-visualization-made-simple-4c0c</guid>
      <description>&lt;p&gt;Benchmarking in Go is straightforward, but visualizing those results? That's where things get messy. When you're comparing different libraries or implementations across various workloads, staring at raw benchmark numbers just doesn't cut it.&lt;/p&gt;

&lt;p&gt;I found myself in this exact situation while working with my message queue library, &lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;varmq&lt;/a&gt;. I needed to compare performance across different scenarios, but existing visualization tools either didn't exist or didn't fit my needs.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Vizb&lt;/strong&gt; – a CLI tool that transforms Go benchmark results into beautiful, interactive HTML charts with a single command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Magic is in the Simplicity
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;-bench&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;^&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nt"&gt;-json&lt;/span&gt; | vizb &lt;span class="nt"&gt;-o&lt;/span&gt; output.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One command, and you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive HTML charts&lt;/strong&gt; showing execution time, memory usage, and allocations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive design&lt;/strong&gt; that works on any device&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export capability&lt;/strong&gt; – download charts as PNG images directly from your browser&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart grouping&lt;/strong&gt; that automatically organizes your benchmarks by workload and subject&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Features That Actually Matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Multiple Metrics in One View&lt;/strong&gt;: Compare execution time, memory usage, and allocation counts side by side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customizable Units&lt;/strong&gt;: Display metrics in your preferred units (ns/μs/ms/s for time, B/KB/MB/GB for memory).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flexible Input&lt;/strong&gt;: Works with files or piped input – whatever fits your workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JSON Output&lt;/strong&gt;: Need the data in a different format? Use &lt;code&gt;--format json&lt;/code&gt; to get structured data instead of HTML.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Example
&lt;/h2&gt;

&lt;p&gt;Here's how Vizb automatically groups your benchmarks. If you have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BenchmarkEncoder/small/json
BenchmarkEncoder/small/msgpack
BenchmarkEncoder/large/json
BenchmarkEncoder/large/msgpack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vizb creates charts where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test&lt;/strong&gt;: Encoder&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workloads&lt;/strong&gt;: small, large
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subjects&lt;/strong&gt;: json, msgpack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Clean, organized charts that make performance differences immediately obvious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;p&gt;When you're optimizing code or choosing between libraries, you need insights, not raw numbers. Vizb turns your benchmark data into actionable visualizations that help you make better decisions faster.&lt;/p&gt;

&lt;p&gt;Check it out: &lt;a href="https://github.com/goptics/vizb" rel="noopener noreferrer"&gt;github.com/goptics/vizb&lt;/a&gt;&lt;br&gt;
Check out the live chart benchmarks: &lt;a href="https://varmq-benchmarks.netlify.app" rel="noopener noreferrer"&gt;varmq-benchmark&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Install it with: &lt;code&gt;go install github.com/goptics/vizb&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Happy benchmarking! 🚀&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>showdev</category>
      <category>go</category>
      <category>cli</category>
    </item>
    <item>
      <title>sync.Cond in Go: Efficient Goroutine Signaling Without Channels</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Wed, 04 Jun 2025 12:50:51 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/synccond-in-go-efficient-goroutine-signaling-without-channels-2eh3</link>
      <guid>https://dev.to/fahimfaisaal/synccond-in-go-efficient-goroutine-signaling-without-channels-2eh3</guid>
      <description>&lt;p&gt;Concurrency in Go often brings us to channels, but there’s another synchronization primitive that may be exactly what you need in some scenarios: &lt;code&gt;sync.Cond&lt;/code&gt;. If you’ve ever wondered why you’d reach for a &lt;code&gt;sync.Cond&lt;/code&gt; instead of using channels alone, this article is for you. By the end, you’ll see a simple custom implementation, understand how the real &lt;code&gt;sync.Cond&lt;/code&gt; works under the hood, and know when to choose it in your own projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Use &lt;code&gt;sync.Cond&lt;/code&gt;?
&lt;/h2&gt;

&lt;p&gt;Most Go developers instinctively reach for channels to coordinate goroutines: sending values, waiting for results, and so on. However, channels also carry data. What if all you need is a simple “wake-up” signal, without any payload? That’s exactly where &lt;code&gt;sync.Cond&lt;/code&gt; shines. It’s a lightweight way to block one or more goroutines until a condition becomes true, without transferring actual data.&lt;/p&gt;

&lt;p&gt;Think of it like a broadcast system: goroutines can call &lt;code&gt;Wait()&lt;/code&gt; and suspend until somebody calls &lt;code&gt;Signal()&lt;/code&gt; (wake a single waiter) or &lt;code&gt;Broadcast()&lt;/code&gt; (wake all waiters). Underneath, &lt;code&gt;sync.Cond&lt;/code&gt; doesn’t allocate a channel for each goroutine; instead, it maintains a small linked list of waiting goroutines, making it more memory-efficient when you just need signaling.&lt;/p&gt;

&lt;p&gt;To illustrate, let’s build our own “poor man’s” condition variable using channels. Once you see the analogy, switching to &lt;code&gt;sync.Cond&lt;/code&gt; becomes straightforward.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a Custom &lt;code&gt;Cond&lt;/code&gt; with Channels
&lt;/h2&gt;

&lt;p&gt;Here’s a minimal struct that mimics &lt;code&gt;sync.Cond&lt;/code&gt; by using a slice of channels and a mutex:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;MyCond&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;chs&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;mu&lt;/span&gt;  &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;chs&lt;/code&gt; holds one channel per waiting goroutine.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mu&lt;/code&gt; ensures that appending or removing from &lt;code&gt;chs&lt;/code&gt; is safe.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Below are three methods &lt;code&gt;Wait()&lt;/code&gt;, &lt;code&gt;Signal()&lt;/code&gt;, and &lt;code&gt;Broadcast()&lt;/code&gt; that emulate the core behavior of &lt;code&gt;sync.Cond&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyCond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// wait for a signal&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyCond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// pick the first channel and send signal&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
    &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// remove that channel from the slice&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;MyCond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Broadcast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{}{}&lt;/span&gt;
        &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// reset the slice so no stale channels remain&lt;/span&gt;
    &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What’s happening here?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Wait()&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Lock the mutex.&lt;/li&gt;
&lt;li&gt;Create a new “signal” channel (&lt;code&gt;ch&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Append it to &lt;code&gt;c.chs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Unlock, then block on &lt;code&gt;&amp;lt;-ch&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When someone calls &lt;code&gt;Signal()&lt;/code&gt; or &lt;code&gt;Broadcast()&lt;/code&gt;, that channel is closed (and a value is sent), letting this goroutine resume.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Signal()&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Lock the mutex.&lt;/li&gt;
&lt;li&gt;If there’s at least one waiting channel, pick the first.&lt;/li&gt;
&lt;li&gt;Send a dummy &lt;code&gt;struct{}{}&lt;/code&gt; onto it, then &lt;code&gt;close(ch)&lt;/code&gt; so that any extra &lt;code&gt;&amp;lt;-ch&lt;/code&gt; receives don’t hang.&lt;/li&gt;
&lt;li&gt;Remove that channel from the slice.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Broadcast()&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Lock the mutex.&lt;/li&gt;
&lt;li&gt;Loop over every waiting channel: send a dummy signal and close it.&lt;/li&gt;
&lt;li&gt;Reset the slice to empty, so future waiters start fresh.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This simple approach shows how condition variables signal “ready to go” without passing actual payloads just signals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Our Custom &lt;code&gt;MyCond&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;To see &lt;code&gt;MyCond&lt;/code&gt; in action, imagine spawning multiple worker goroutines that all wait for a signal. Then, from another goroutine, send one signal at a time. Finally, switch to broadcasting to wake everyone at once.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;MyCond&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// add tasks count to the wait group&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// create separate go routine for each task&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Waiting"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Done"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// send signal to one routine in every 1 second&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="c"&gt;// wait for all routines to finish&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The output
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kjw2coi8w8tqr3j5j5e.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5kjw2coi8w8tqr3j5j5e.gif" alt="Signal Outputs Preview" width="720" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you run that, each goroutine hangs on &lt;code&gt;cond.Wait()&lt;/code&gt;. Every second, &lt;code&gt;Signal()&lt;/code&gt; wakes exactly one goroutine, until all 5 finish.&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching to Broadcast
&lt;/h3&gt;

&lt;p&gt;Instead of signaling one by one, you can broadcast after a delay to wake all of them at once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// just change the signal to broadcast go routine&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// - for range tasks {&lt;/span&gt;
    &lt;span class="c"&gt;// -    time.Sleep(1 * time.Second)&lt;/span&gt;
    &lt;span class="c"&gt;// -    cond.Signal() // send signal to one routine in every 1 second&lt;/span&gt;
    &lt;span class="c"&gt;// - }&lt;/span&gt;

    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Broadcast&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// send signal to all routines at once after 2 seconds&lt;/span&gt;
&lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The output
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwic55gk8ltg34fn0uve1.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwic55gk8ltg34fn0uve1.gif" alt="Cond Broadcast Preview" width="760" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With this modification, all 5 goroutines sleep in &lt;code&gt;cond.Wait()&lt;/code&gt;. After two seconds, a single &lt;code&gt;Broadcast()&lt;/code&gt; wakes everybody, and you’ll see all “Done” messages in rapid succession.&lt;/p&gt;

&lt;h2&gt;
  
  
  Replacing &lt;code&gt;MyCond&lt;/code&gt; with &lt;code&gt;sync.Cond&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Once you’ve verified the custom behavior, swapping in the real &lt;code&gt;sync.Cond&lt;/code&gt; is straightforward. Anywhere you wrote:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;//  cond := &amp;amp;MyCond{}&lt;/span&gt;
&lt;span class="n"&gt;cond&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutex&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;

&lt;span class="c"&gt;// cond.Wait()&lt;/span&gt;
&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;L&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll get the same “Waiting … Done” behavior as before, but now backed by the official, optimized implementation.&lt;/p&gt;

&lt;p&gt;Under the hood, &lt;code&gt;sync.Cond&lt;/code&gt; doesn’t spin up a channel per waiter. Instead it uses an internal &lt;code&gt;notifyList&lt;/code&gt; a small doubly linked list of waiting goroutines ) and uses low‐level runtime primitives to park and wake goroutines. Each call to &lt;code&gt;Wait()&lt;/code&gt; enqueues the goroutine on that list. &lt;code&gt;Signal()&lt;/code&gt; removes one link and wakes its goroutine; &lt;code&gt;Broadcast()&lt;/code&gt; traverses the whole list and wakes every waiter. Memory-wise, this is much cheaper than allocating a channel per waiter, especially if you have hundreds or thousands of goroutines occasionally blocking on the same condition.&lt;/p&gt;

&lt;p&gt;For a deeper dive, check out the source code for &lt;a href="https://github.com/golang/go/blob/master/src/sync/cond.go" rel="noopener noreferrer"&gt;&lt;code&gt;sync.Cond&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Choose &lt;code&gt;sync.Cond&lt;/code&gt; Over Channels
&lt;/h2&gt;

&lt;p&gt;Here are a few scenarios where &lt;code&gt;sync.Cond&lt;/code&gt; makes sense:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simple Signaling&lt;/strong&gt;&lt;br&gt;
If goroutines only need a “go now” notification no data passed &lt;code&gt;sync.Cond&lt;/code&gt; provides a clearer, more intent-expressive API than channels filled with dummy values.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Broadcast Semantics&lt;/strong&gt;&lt;br&gt;
Channels lack a built-in “wake everyone” primitive. You could loop over a list of channels, but managing that list is extra boilerplate. &lt;code&gt;sync.Cond.Broadcast()&lt;/code&gt; does exactly what it says: wake all waiters at once.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lower Memory Overhead&lt;/strong&gt;&lt;br&gt;
Each Go channel has internal buffers, mutexes, and so on. If you merely need a “signal,” channels allocate more than necessary. A &lt;code&gt;sync.Cond&lt;/code&gt; maintains a minimal linked list of waiters, which is especially noticeable if you have thousands of goroutines waiting occasionally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Condition-Based Waiting&lt;/strong&gt;&lt;br&gt;
Often you combine &lt;code&gt;sync.Cond&lt;/code&gt; with a separate shared value. For example:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;   &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;conditionMet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="n"&gt;cond&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
   &lt;span class="p"&gt;}&lt;/span&gt;
   &lt;span class="c"&gt;// now the condition is true; proceed&lt;/span&gt;
   &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This “wait in a loop” pattern is common in concurrent structures like pools, queues, or buffered buffers. Channels alone can’t you’d have to juggle extra variables or use &lt;code&gt;select&lt;/code&gt;, which can get messy.&lt;/p&gt;

&lt;p&gt;In short, if your goroutines coordinate purely on a boolean or numerical condition and you want to wake either one waiter or all waiters &lt;code&gt;sync.Cond&lt;/code&gt; shines. If you need to send actual data, channels remain the more idiomatic choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Open-Source Promotions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt; - A Simplest Storage-Agnostic and Zero-dep Message Queue for Your Concurrent Go Program&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/goptics/vizb" rel="noopener noreferrer"&gt;Vizb&lt;/a&gt; - An interactive go benchmarks visualizer&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>go</category>
      <category>beginners</category>
    </item>
    <item>
      <title>VarMQ Tuning Worker Pool</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Sun, 18 May 2025 03:34:15 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/varmq-tuning-worker-pool-1bo6</link>
      <guid>https://dev.to/fahimfaisaal/varmq-tuning-worker-pool-1bo6</guid>
      <description>&lt;p&gt;I've created a "tune API" for the next version of &lt;strong&gt;&lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt;&lt;/strong&gt;. Essentially, "Tune" allows you to increase or decrease the size of the worker/thread pool at runtime.&lt;/p&gt;

&lt;p&gt;For example, when the load on your server is high, you'll need to process more concurrent jobs. Conversely, when the load is low, you don't need as many workers, because workers consume resources.&lt;/p&gt;

&lt;p&gt;Therefore, based on your custom logic, you can dynamically change the worker pool size using this tune API.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/XQZtr9DxCsw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In this video, I've &lt;strong&gt;enqueued 1000 jobs&lt;/strong&gt; into &lt;strong&gt;&lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt;&lt;/strong&gt;, and I've set the initial worker pool size to &lt;strong&gt;10&lt;/strong&gt; (the concurrency value).&lt;/p&gt;

&lt;p&gt;Every second, using the tune API, I'm increasing the worker pool size by 10 until it &lt;strong&gt;reaches 100&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once it reaches a size of &lt;strong&gt;100&lt;/strong&gt;, then I start removing &lt;strong&gt;10 workers&lt;/strong&gt; at a time from the pool.&lt;/p&gt;

&lt;p&gt;This way, I'm decreasing and then increasing the worker pool size.&lt;/p&gt;

&lt;p&gt;Cool, right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt;&lt;/strong&gt; primarily uses an &lt;strong&gt;Event-Loop&lt;/strong&gt; internally to handle this concurrency. &lt;/p&gt;

&lt;p&gt;This event loop checks if there are any pending jobs in the queue and if any workers are available in the worker pool. If there are, it distributes jobs to all available workers and then goes back into sleep mode.&lt;/p&gt;

&lt;p&gt;When a worker becomes free, it then tells the event loop, "Hey, I'm free now; if you have any jobs, you can give them to me."&lt;/p&gt;

&lt;p&gt;The event loop then checks again if there are any pending jobs in the queue. If there are, it continues to distribute them to the workers.&lt;/p&gt;

&lt;p&gt;This is &lt;strong&gt;&lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ's&lt;/a&gt;&lt;/strong&gt; concurrency model.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>go</category>
      <category>worker</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Story of Building a Storage-Agnostic Message Queue</title>
      <dc:creator>Fahim Faisaal</dc:creator>
      <pubDate>Fri, 09 May 2025 13:41:34 +0000</pubDate>
      <link>https://dev.to/fahimfaisaal/a-story-of-building-a-storage-agnostic-message-queue-3lo4</link>
      <guid>https://dev.to/fahimfaisaal/a-story-of-building-a-storage-agnostic-message-queue-3lo4</guid>
      <description>&lt;p&gt;A year ago, I was knee-deep in Golang, trying to build a simple &lt;a href="https://github.com/fahimfaisaal/exp-go-concurrency/blob/main/concurrent-queue/queue.go" rel="noopener noreferrer"&gt;concurrent queue&lt;/a&gt; as a learning project. Coming from a Node.js background, where I’d spent years working with tools like BullMQ and RabbitMQ, Go’s concurrency model felt like a puzzle. My first attempt a minimal queue with round-robin channel selection was, well, buggy. Let’s just say it worked until it didn’t.&lt;/p&gt;

&lt;p&gt;But that’s how learning goes, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Spark of an Idea
&lt;/h2&gt;

&lt;p&gt;In my professional work, I’ve used tools like BullMQ and RabbitMQ for event-driven solutions, and p-queue and p-limit for handling concurrency. Naturally, I began wondering if there were similar tools in Go. I found packages like &lt;code&gt;asynq&lt;/code&gt;, &lt;code&gt;ants&lt;/code&gt;, and various worker pools solid, battle-tested options. But suddenly, a thought struck me: what if I built something different? A package with zero dependencies, high concurrency control, and designed as a message queue rather than submitting functions?&lt;/p&gt;

&lt;p&gt;With that spark, I started building my first Go package, released it, and named it &lt;a href="https://pkg.go.dev/github.com/fahimfaisaal/gocq" rel="noopener noreferrer"&gt;Gocq&lt;/a&gt; (Go Concurrent Queue). The core API was straightforward, as you can see here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Create a queue with 2 concurrent workers&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gocq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;500&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c"&gt;// Add a single job&lt;/span&gt;
&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 10&lt;/span&gt;

&lt;span class="c"&gt;// Add multiple jobs&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Output: 2, 4, 6, 8, 10 (unordered)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the excitement, I &lt;a href="https://www.reddit.com/r/golang/comments/1j5k9g8/i_built_a_concurrency_queue_that_might_bring_some/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button" rel="noopener noreferrer"&gt;posted&lt;/a&gt; it on Reddit. To my surprise, it got traction upvotes, comments, and appreciations. Here’s the fun part: coming from the Node.js ecosystem, I totally messed up Go’s package system at first.&lt;/p&gt;

&lt;p&gt;Within a week, I released the next version with a few major changes and shared it on Reddit &lt;a href="https://www.reddit.com/r/golang/comments/1jcdsxo/gocq_is_now_on_v2_now_faster_smarter_and_fancier/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button" rel="noopener noreferrer"&gt;again&lt;/a&gt;. More feedback rolled in, and one person asked for "persistence abstractions support".&lt;/p&gt;

&lt;h2&gt;
  
  
  The Missing Piece
&lt;/h2&gt;

&lt;p&gt;That hit home, I’d felt this gap before, &lt;strong&gt;Persistence&lt;/strong&gt;. It’s the backbone of any reliable queue system. Without persistence, the package wouldn’t be complete. But then a question is: if I add persistence, would I have to tie it to a specific tool like Redis or another database?&lt;/p&gt;

&lt;p&gt;I didn’t want to lock users into Redis, SQLite, or any specific storage. What if the queue could adapt to any database?&lt;/p&gt;

&lt;p&gt;So I tore gocq apart.&lt;/p&gt;

&lt;p&gt;I rewrote most of it, splitting the core into two parts: a worker pool and a queue interface. The worker would pull jobs from the queue without caring where those jobs lived.&lt;/p&gt;

&lt;p&gt;The result? &lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;VarMQ&lt;/a&gt;, a queue system that doesn’t care if your storage is Redis, SQLite, or even in-memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works Now
&lt;/h2&gt;

&lt;p&gt;Imagine you need a simple, in-memory queue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;varmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="n"&gt;varmq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="c"&gt;// your heavy work&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BindQueue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// Done. No setup, no dependencies.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you want persistence, just plug in an adapter. Let’s say SQLite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/goptics/sqliteq"&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sqliteq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"test.db"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithPersistentQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Now your jobs survive restarts.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or Redis for distributed workloads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/goptics/redisq"&lt;/span&gt;

&lt;span class="n"&gt;rdb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;redisq&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"redis://localhost:6379"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;pq&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rdb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewDistributedQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"transactions"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithDistributedQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pq&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// Scale across servers.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The magic? The worker doesn’t know—or care—what’s behind the queue. It just processes jobs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons from the Trenches
&lt;/h2&gt;

&lt;p&gt;Building this taught me two big things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity is hard&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback is gold&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Message queues are everywhere—order processing, notifications, data pipelines. But not every project needs Redis. Sometimes you just want SQLite for simplicity, or to switch databases later without rewriting code.&lt;/p&gt;

&lt;p&gt;With Varmq, you’re not boxed in. Need persistence? Add it. Need scale? Swap adapters. It’s like LEGO for queues.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;The next step is to integrate the PostgreSQL adapter and a monitoring system. &lt;/p&gt;

&lt;p&gt;If you’re curious, check out &lt;a href="https://github.com/goptics/varmq" rel="noopener noreferrer"&gt;Varmq on GitHub&lt;/a&gt;. Feel free to share your thoughts and opinions in the comments below, and let's make this Better together.&lt;/p&gt;

</description>
      <category>go</category>
      <category>queue</category>
      <category>distributedsystems</category>
      <category>worker</category>
    </item>
  </channel>
</rss>
