<?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: aymane</title>
    <description>The latest articles on DEV Community by aymane (@aymane-belassiria).</description>
    <link>https://dev.to/aymane-belassiria</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%2F981420%2F04c0c44e-29cb-49ca-8957-03a558a1b1a4.jpg</url>
      <title>DEV Community: aymane</title>
      <link>https://dev.to/aymane-belassiria</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aymane-belassiria"/>
    <language>en</language>
    <item>
      <title>OBDA and Virtual Knowledge Graphs: A Practical Introduction</title>
      <dc:creator>aymane</dc:creator>
      <pubDate>Sat, 12 Sep 2026 22:45:05 +0000</pubDate>
      <link>https://dev.to/aymane-belassiria/obda-and-virtual-knowledge-graphs-a-practical-introduction-pll</link>
      <guid>https://dev.to/aymane-belassiria/obda-and-virtual-knowledge-graphs-a-practical-introduction-pll</guid>
      <description>&lt;p&gt;When working with enterprise data, one problem appears again and again: the data is spread across different systems, but applications need to work with it as if it belonged to one coherent model.&lt;/p&gt;

&lt;p&gt;A company might have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PostgreSQL databases&lt;/li&gt;
&lt;li&gt;Oracle databases&lt;/li&gt;
&lt;li&gt;data warehouses&lt;/li&gt;
&lt;li&gt;legacy systems&lt;/li&gt;
&lt;li&gt;APIs&lt;/li&gt;
&lt;li&gt;CSV files&lt;/li&gt;
&lt;li&gt;different databases owned by different teams&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The schemas are usually different, even when the systems describe related things.&lt;/p&gt;

&lt;p&gt;For example, one system might have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customer_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client_number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;account_id
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All three might represent the same business concept: a customer.&lt;/p&gt;

&lt;p&gt;One approach is to copy everything into a new database or knowledge graph.&lt;/p&gt;

&lt;p&gt;Another approach is to leave the data where it is and put a semantic layer on top of it.&lt;/p&gt;

&lt;p&gt;This is where &lt;strong&gt;Ontology-Based Data Access (OBDA)&lt;/strong&gt; and &lt;strong&gt;Virtual Knowledge Graphs (VKGs)&lt;/strong&gt; come in.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is OBDA?
&lt;/h1&gt;

&lt;p&gt;Ontology-Based Data Access is an approach where users access data through an ontology instead of directly interacting with the schemas of the underlying databases.&lt;/p&gt;

&lt;p&gt;The ontology provides the conceptual model.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
Order
Product

Customer ── places ──&amp;gt; Order
Order ── contains ──&amp;gt; Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database doesn't have to use these names.&lt;/p&gt;

&lt;p&gt;It could look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;crm_customer
------------
cust_no
cust_nm
country_code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sales_order
-----------
order_no
cust_no
total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ontology can still expose these tables as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
Order
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connection between the physical schema and the ontology is defined through &lt;strong&gt;mappings&lt;/strong&gt;.&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%2Ftizs42l0dgoa7gje4a58.jpeg" 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%2Ftizs42l0dgoa7gje4a58.jpeg" alt=" " width="606" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The important idea is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ontology
   ↓
Mappings
   ↓
Data sources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ontology describes what the data means.&lt;/p&gt;

&lt;p&gt;The mappings describe where that data comes from.&lt;/p&gt;

&lt;p&gt;The actual data remains in the original systems.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Three Main Components
&lt;/h1&gt;

&lt;p&gt;An OBDA system can be understood through three main components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ontology&lt;/li&gt;
&lt;li&gt;Mappings&lt;/li&gt;
&lt;li&gt;Data sources&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Ontology
&lt;/h2&gt;

&lt;p&gt;The ontology describes the concepts and relationships that users work with.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
Order
Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer ── places ──&amp;gt; Order
Order ── contains ──&amp;gt; Product
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can also contain additional semantic information.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PremiumCustomer subclassOf Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Order hasCustomer Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ontology represents the domain from the point of view of the application or user.&lt;/p&gt;

&lt;p&gt;It does not need to mirror the database schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Mappings
&lt;/h2&gt;

&lt;p&gt;Mappings connect the ontology to the physical data.&lt;/p&gt;

&lt;p&gt;Suppose the database contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&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;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A mapping can define that the rows returned by this query represent instances of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers.name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;represents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer.name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers.country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;represents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer.country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database might therefore look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers
---------
id
name
country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;while the semantic model looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
 ├── id
 ├── name
 └── country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapping connects the two.&lt;/p&gt;

&lt;p&gt;This separation is important because the physical schema can change without necessarily changing the conceptual model.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Data Sources
&lt;/h2&gt;

&lt;p&gt;The data sources are the systems containing the actual data.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL
Oracle
MySQL
SQL Server
Data warehouse
Legacy database
API
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An OBDA system can sit above these sources and provide a semantic interface.&lt;/p&gt;

&lt;p&gt;The important point is that the data does not have to be copied into the semantic layer.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is a Virtual Knowledge Graph?
&lt;/h1&gt;

&lt;p&gt;A knowledge graph represents information as entities and relationships.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer123 ── places ──&amp;gt; Order456

Order456 ── contains ──&amp;gt; Product789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With RDF, this can be represented as triples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer123 → places → Order456
Order456    → contains → Product789
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;Virtual Knowledge Graph&lt;/strong&gt; provides this graph representation without requiring the underlying data to be physically stored as RDF.&lt;/p&gt;

&lt;p&gt;The database remains the source of truth.&lt;/p&gt;

&lt;p&gt;Conceptually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Relational database
        ↓
     Mappings
        ↓
Virtual RDF graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph is virtual because it is derived from the source data when needed.&lt;/p&gt;

&lt;h1&gt;
  
  
  Materialized Knowledge Graph vs Virtual Knowledge Graph
&lt;/h1&gt;

&lt;p&gt;There are two different approaches.&lt;/p&gt;

&lt;h2&gt;
  
  
  Materialized Knowledge Graph
&lt;/h2&gt;

&lt;p&gt;The data is extracted and loaded into a graph database.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   │
   │ ETL
   ▼
Knowledge Graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph contains its own copy of the data.&lt;/p&gt;

&lt;p&gt;This can be useful when graph queries need to be served independently of the original database.&lt;/p&gt;

&lt;p&gt;But it introduces another copy of the data.&lt;/p&gt;

&lt;p&gt;You now have to think about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source database
       ↓
ETL
       ↓
Knowledge graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and keeping the two synchronized.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual Knowledge Graph
&lt;/h2&gt;

&lt;p&gt;With a VKG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   │
   ▼
Mappings
   │
   ▼
Virtual graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph is a semantic view over the existing data.&lt;/p&gt;

&lt;p&gt;There is no requirement to first copy every row into a graph database.&lt;/p&gt;

&lt;p&gt;If the database changes, queries against the virtual graph can see the new data because the data is still being obtained from the source.&lt;/p&gt;

&lt;p&gt;This is one of the main reasons VKGs are useful when organizations already have large relational systems.&lt;/p&gt;

&lt;h1&gt;
  
  
  Querying a Virtual Knowledge Graph
&lt;/h1&gt;

&lt;p&gt;A VKG is normally queried using &lt;strong&gt;SPARQL&lt;/strong&gt;, the standard query language for RDF.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sparql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;?customer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;?customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="ss"&gt;hasCountry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Morocco"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user is querying the semantic model.&lt;/p&gt;

&lt;p&gt;The database does not necessarily contain a column called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hasCountry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mapping might tell the system that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:hasCountry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;corresponds to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers.country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The VKG system can then generate a database query similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;customers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;country&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'Morocco'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user works with the ontology.&lt;/p&gt;

&lt;p&gt;The database executes SQL.&lt;/p&gt;

&lt;p&gt;The system connects the two.&lt;/p&gt;

&lt;h1&gt;
  
  
  Query Rewriting
&lt;/h1&gt;

&lt;p&gt;Query rewriting is one of the central mechanisms behind OBDA.&lt;/p&gt;

&lt;p&gt;Consider this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sparql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;?customer&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;?customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="ss"&gt;hasCountry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Morocco"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The query is expressed using the ontology.&lt;/p&gt;

&lt;p&gt;The OBDA system has to determine how that semantic query can be answered using the underlying database.&lt;/p&gt;

&lt;p&gt;A simplified process is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPARQL query
     ↓
Ontology-level rewriting
     ↓
Mapping expansion
     ↓
SQL query
     ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:hasCountry
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could be mapped to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers.country
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;:Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could be mapped to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;customers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resulting SQL can then be executed directly by the database.&lt;/p&gt;

&lt;h1&gt;
  
  
  The VKG Query Pipeline
&lt;/h1&gt;

&lt;p&gt;A simplified VKG query pipeline looks like this:&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%2Fbk4t8vb3123tu4mr8u1k.jpeg" 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%2Fbk4t8vb3123tu4mr8u1k.jpeg" alt=" " width="741" height="380"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPARQL query
      ↓
Query rewriting
      ↓
Rewritten query
      ↓
Mapping unfolding
      ↓
SQL query
      ↓
Database
      ↓
SQL result
      ↓
SPARQL result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two important transformations here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rewriting
&lt;/h3&gt;

&lt;p&gt;The ontology is used to expand the query according to the semantic relationships defined in the ontology.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unfolding
&lt;/h3&gt;

&lt;p&gt;The mappings are used to translate the ontology-level query into a query over the actual data sources.&lt;/p&gt;

&lt;p&gt;The result is a database query that can be executed by the underlying system.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why Use an Ontology?
&lt;/h1&gt;

&lt;p&gt;A database schema tells you how data is stored.&lt;/p&gt;

&lt;p&gt;An ontology is concerned with what the data means.&lt;/p&gt;

&lt;p&gt;Consider two databases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database A

client_id
client_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database B

customer_number
customer_name
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the database perspective, these are different schemas.&lt;/p&gt;

&lt;p&gt;From the business perspective, both might represent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An ontology can provide that common conceptual layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;             Customer
             /      \
            /        \
       Database A   Database B
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The applications don't need to know that one system calls it &lt;code&gt;client_id&lt;/code&gt; and another calls it &lt;code&gt;customer_number&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  OBDA vs Data Warehouse
&lt;/h1&gt;

&lt;p&gt;OBDA and a data warehouse solve different problems.&lt;/p&gt;

&lt;p&gt;A data warehouse physically stores integrated data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source A ─┐
Source B ─┼──&amp;gt; ETL ──&amp;gt; Data Warehouse
Source C ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OBDA instead creates a semantic access layer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Ontology
                    │
                 Mappings
                    │
          ┌─────────┼─────────┐
          ▼         ▼         ▼
       Source A  Source B  Source C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't mean one approach is better than the other.&lt;/p&gt;

&lt;p&gt;They can also be used together.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Semantic Layer
                          │
             ┌────────────┼────────────┐
             ▼            ▼            ▼
         PostgreSQL    Warehouse     Data Lake
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ontology provides a common vocabulary while the underlying systems remain responsible for storing and processing the data.&lt;/p&gt;

&lt;h1&gt;
  
  
  OBDA vs Knowledge Graph
&lt;/h1&gt;

&lt;p&gt;The terms OBDA and knowledge graph are related, but they are not the same thing.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;knowledge graph&lt;/strong&gt; is a way of representing information as entities and relationships.&lt;/p&gt;

&lt;p&gt;OBDA is an architecture for accessing data through an ontology.&lt;/p&gt;

&lt;p&gt;A knowledge graph can be materialized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
ETL
   ↓
Graph database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or virtual:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Database
   ↓
Mappings
   ↓
Virtual Knowledge Graph
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So a VKG can be viewed as a knowledge graph that is exposed virtually rather than requiring the entire graph to be materialized first.&lt;/p&gt;

&lt;h1&gt;
  
  
  OBDA Architecture
&lt;/h1&gt;

&lt;p&gt;Putting the pieces together:&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%2Fp2lhembeasevlfjj6v8e.jpeg" 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%2Fp2lhembeasevlfjj6v8e.jpeg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 Application
                      │
                      │ SPARQL
                      ▼
                 ┌───────────┐
                 │ Ontology  │
                 └─────┬─────┘
                       │
                    Mappings
                       │
              ┌────────┼────────┐
              ▼        ▼        ▼
           Database  Database  Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A real OBDA implementation adds more components around this basic model.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPARQL
   ↓
Query Rewriter
   ↓
Mapping / Query Unfolder
   ↓
SQL Generator
   ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact architecture depends on the implementation.&lt;/p&gt;

&lt;h1&gt;
  
  
  Where the Complexity Actually Goes
&lt;/h1&gt;

&lt;p&gt;At first glance, the architecture looks simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ontology
   +
Mappings
   +
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difficult part starts when the semantic query becomes complicated.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
    ↓
Order
    ↓
Product
    ↓
Supplier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;might correspond to several joins in the underlying relational schema.&lt;/p&gt;

&lt;p&gt;The OBDA engine needs to transform the semantic query into an efficient database query.&lt;/p&gt;

&lt;p&gt;That means the system has to deal with things such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;query rewriting&lt;/li&gt;
&lt;li&gt;SQL generation&lt;/li&gt;
&lt;li&gt;join optimization&lt;/li&gt;
&lt;li&gt;database constraints&lt;/li&gt;
&lt;li&gt;query planning&lt;/li&gt;
&lt;li&gt;source capabilities&lt;/li&gt;
&lt;li&gt;different SQL dialects&lt;/li&gt;
&lt;li&gt;large intermediate results&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why OBDA is closely related to database query processing.&lt;/p&gt;

&lt;p&gt;The ontology gives the system the semantic model, but the database still has to execute the final query efficiently.&lt;/p&gt;

&lt;h1&gt;
  
  
  A Simple Mental Model
&lt;/h1&gt;

&lt;p&gt;A useful way to think about the whole architecture is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ontology
"What does this data mean?"

        ↓

Mappings
"Where does this information come from?"

        ↓

VKG
"What does the integrated data look like semantically?"

        ↓

Query rewriting
"How can this semantic query be answered?"

        ↓

SQL
"How do I ask the underlying database?"

        ↓

Database
"Execute it."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That separation is the core idea behind OBDA and Virtual Knowledge Graphs.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;OBDA provides a way to put a semantic layer over existing data sources.&lt;/p&gt;

&lt;p&gt;The basic model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Ontology
    +
Mappings
    +
Data Sources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Virtual Knowledge Graph builds on this idea by exposing the underlying data as a virtual RDF graph.&lt;/p&gt;

&lt;p&gt;The data can remain in relational databases while users query it through concepts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Customer
Order
Product
Supplier
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system then handles the translation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPARQL
   ↓
Ontology reasoning / rewriting
   ↓
Mapping unfolding
   ↓
SQL
   ↓
Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main advantage is the separation between &lt;strong&gt;how data is stored&lt;/strong&gt; and &lt;strong&gt;how data is understood&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The database can continue using tables and columns designed for storage and operations, while the semantic layer provides a model designed around the domain.&lt;/p&gt;

&lt;p&gt;That separation is what makes OBDA and Virtual Knowledge Graphs useful when working with heterogeneous data sources without immediately moving everything into a new physical knowledge graph.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>ontology</category>
      <category>knowledgegraph</category>
    </item>
    <item>
      <title>My Journey Into Ontology Engineering</title>
      <dc:creator>aymane</dc:creator>
      <pubDate>Thu, 10 Sep 2026 20:29:34 +0000</pubDate>
      <link>https://dev.to/aymane-belassiria/my-journey-into-ontology-engineering-5095</link>
      <guid>https://dev.to/aymane-belassiria/my-journey-into-ontology-engineering-5095</guid>
      <description>&lt;p&gt;Over the past few months, I have been exploring a topic that has changed the way I think about software, data, and complex systems: &lt;strong&gt;ontology engineering&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My journey started with theory.&lt;/p&gt;

&lt;p&gt;I began reading &lt;strong&gt;C. Maria Keet's &lt;em&gt;An Introduction to Ontology Engineering&lt;/em&gt;&lt;/strong&gt;, and the book helped me build a much stronger foundation for understanding what an ontology actually is, how concepts and relationships can be formally represented, and how ontology engineering connects to practical software systems.&lt;/p&gt;

&lt;p&gt;But the more I read, the more I wanted to move beyond theory.&lt;/p&gt;

&lt;p&gt;I started asking a simple question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What happens when ontology engineering meets real-world software?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question led me into several fascinating areas.&lt;/p&gt;

&lt;p&gt;I began exploring &lt;strong&gt;Ontology-Based Data Access (OBDA)&lt;/strong&gt; and the idea of using an ontology as a semantic layer between applications and underlying data sources.&lt;/p&gt;

&lt;p&gt;Instead of applications only thinking in terms of tables, columns, and database schemas, an ontology can provide a higher-level representation of concepts and relationships.&lt;/p&gt;

&lt;p&gt;I also started exploring &lt;strong&gt;knowledge graphs&lt;/strong&gt; and how connected entities can provide context that isolated data often cannot.&lt;/p&gt;

&lt;p&gt;From there, my curiosity moved toward another challenge: &lt;strong&gt;decision-making&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;How can a system use ontology-based knowledge to understand a situation?&lt;/p&gt;

&lt;p&gt;How can relationships between entities provide context for a decision?&lt;/p&gt;

&lt;p&gt;And how can language contribute to that process?&lt;/p&gt;

&lt;p&gt;This led me to explore the idea of interpreting &lt;strong&gt;decision phrases and lexical expressions&lt;/strong&gt; and connecting them to concepts represented in an ontology.&lt;/p&gt;

&lt;p&gt;A phrase can contain intent, context, entities, relationships, and domain-specific terminology.&lt;/p&gt;

&lt;p&gt;The challenge becomes connecting human language with structured knowledge.&lt;/p&gt;

&lt;p&gt;I also became increasingly interested in how ontology-driven approaches are used in modern systems involving &lt;strong&gt;AI, knowledge representation, complex operational environments, and large-scale data integration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I have been particularly interested in publicly available examples of ontology-driven platforms such as &lt;a href="https://www.palantir.com" rel="noopener noreferrer"&gt;Palantir&lt;/a&gt; and the broader challenge of creating systems that can connect people, events, organizations, assets, and information into a meaningful operational model.&lt;/p&gt;

&lt;p&gt;This exploration eventually became practical.&lt;/p&gt;

&lt;p&gt;At my company, I have been working on building an ontology engine with a focus on &lt;strong&gt;resilience and performance&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That engineering work has also given me an opportunity to explore the strengths of both &lt;strong&gt;Go (Rust for incoming usage since im still beginner in Rust ;) )&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Go has helped with building pragmatic, concurrent, and resilient backend components.&lt;/p&gt;

&lt;p&gt;Rust has helped me think more deeply about performance, correctness, memory safety, and strongly structured domain models.&lt;/p&gt;

&lt;p&gt;Together, they have made me increasingly interested in a larger engineering question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How can we build fast, resilient systems that don't just store information—but understand the relationships and meaning behind it?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the beginning of a series where I will share what I am learning and building.&lt;/p&gt;

&lt;p&gt;I'll explore ontology engineering from both perspectives:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;theory and practice.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Topics will include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ontology engineering fundamentals&lt;/li&gt;
&lt;li&gt;C. Maria Keet's work and the foundations that helped me get started&lt;/li&gt;
&lt;li&gt;Ontology-Based Data Access&lt;/li&gt;
&lt;li&gt;Knowledge graphs&lt;/li&gt;
&lt;li&gt;AI and knowledge representation&lt;/li&gt;
&lt;li&gt;Ontology-driven operational systems&lt;/li&gt;
&lt;li&gt;Lexical interpretation and decision phrases&lt;/li&gt;
&lt;li&gt;Decision-making and contextual reasoning&lt;/li&gt;
&lt;li&gt;Building resilient ontology engines&lt;/li&gt;
&lt;li&gt;Go and Rust for high-performance ontology systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm not writing this series because I believe I have all the answers.&lt;/p&gt;

&lt;p&gt;I'm writing it because I believe the best way to understand a complex subject is to &lt;strong&gt;study it, build with it, question it, and share what you learn&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This is the beginning of my journey into ontology engineering.&lt;/p&gt;

&lt;p&gt;And I'm excited to explore where theory meets production software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;This post was handwritten and enhanced with AI, but the entire content was written by me. :)&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Reference
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;C. Maria Keet — &lt;em&gt;An Introduction to Ontology Engineering&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>learning</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>worker pool in golang - part 2</title>
      <dc:creator>aymane</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:59:21 +0000</pubDate>
      <link>https://dev.to/aymane-belassiria/worker-pool-in-golag-part-2-4k49</link>
      <guid>https://dev.to/aymane-belassiria/worker-pool-in-golag-part-2-4k49</guid>
      <description>&lt;p&gt;In &lt;a href="https://dev.to/aymane-belassiria/worker-pool-in-golang-part1-5e60"&gt;Part 1&lt;/a&gt; we&lt;br&gt;
built a worker pool: a &lt;code&gt;jobs&lt;/code&gt; channel, three workers, a &lt;code&gt;results&lt;/code&gt; channel, and a&lt;br&gt;
closer goroutine that calls &lt;code&gt;wg.Wait()&lt;/code&gt; then &lt;code&gt;close(results)&lt;/code&gt;. We also walked&lt;br&gt;
into a deadlock on purpose, because that is how you learn to read a concurrent&lt;br&gt;
program.&lt;/p&gt;

&lt;p&gt;Part 1 was easy in one specific way: &lt;strong&gt;the workers never shared anything.&lt;/strong&gt; Each&lt;br&gt;
worker read from a channel and wrote to a channel, and channels do the&lt;br&gt;
synchronization for you. Channels are Go's "share memory by communicating"&lt;br&gt;
answer.&lt;/p&gt;

&lt;p&gt;In this part we do the opposite. We add a counter that every worker touches at&lt;br&gt;
the same time, and we protect it with &lt;code&gt;sync.Mutex&lt;/code&gt;. Then we prove it works with&lt;br&gt;
&lt;code&gt;go test&lt;/code&gt;, &lt;code&gt;go test -race&lt;/code&gt; and &lt;code&gt;go test -bench&lt;/code&gt;, and - the interesting part - we&lt;br&gt;
delete the mutex and watch the race detector tear the program apart.&lt;/p&gt;

&lt;p&gt;The full source is in&lt;br&gt;
&lt;a href="https://github.com/aymane-belassiria/worker-pool-series" rel="noopener noreferrer"&gt;&lt;code&gt;worker-pool-v2&lt;/code&gt;&lt;/a&gt;, and&lt;br&gt;
the workflow diagram for this part is in &lt;code&gt;diagram.md&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we are adding
&lt;/h2&gt;

&lt;p&gt;Four steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;code&gt;Stats&lt;/code&gt; struct that counts processed and failed jobs.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;sync.Mutex&lt;/code&gt; inside it, so concurrent increments are safe.&lt;/li&gt;
&lt;li&gt;Tests: unit, concurrent, race, benchmark.&lt;/li&gt;
&lt;li&gt;Remove the mutex, run &lt;code&gt;-race&lt;/code&gt;, and read the report.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Step 1: the Stats struct
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// adding stats struct to follow the number of jobs successed and failed&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Stats&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;JobProcessed&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;JobFailed&lt;/span&gt;    &lt;span class="kt"&gt;byte&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;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&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;s&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="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&lt;/span&gt;&lt;span class="o"&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;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;s&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;s&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="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&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;Three things worth saying out loud.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mutex lives inside the struct it protects.&lt;/strong&gt; Not next to it, not in a&lt;br&gt;
global. When the lock and the data travel together, nobody can pass you the&lt;br&gt;
counter without also passing you the thing that guards it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The methods have pointer receivers.&lt;/strong&gt; &lt;code&gt;func (s *Stats)&lt;/code&gt;, not &lt;code&gt;func (s Stats)&lt;/code&gt;.&lt;br&gt;
A value receiver would copy the struct - including the mutex - and you would lock&lt;br&gt;
a private copy that nobody else can see. &lt;code&gt;go vet&lt;/code&gt; catches this one for you&lt;br&gt;
(&lt;code&gt;passes lock by value&lt;/code&gt;), and it is a mistake worth catching, because the code&lt;br&gt;
still compiles and still runs and is simply wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;defer s.mu.Unlock()&lt;/code&gt; right after &lt;code&gt;Lock()&lt;/code&gt;.&lt;/strong&gt; The unlock runs even if the body&lt;br&gt;
panics or returns early. In a two-line function this looks like ceremony. In a&lt;br&gt;
twenty-line function with three &lt;code&gt;return&lt;/code&gt; statements it is the difference between&lt;br&gt;
working code and a permanently locked mutex.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2: wiring it into the worker
&lt;/h2&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;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&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;results&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Stats&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="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="c"&gt;//iterate over incoming jobs from the channel and process them&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&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;jobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processed&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"job %d is being processed by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c"&gt;//simulate a failure&lt;/span&gt;
        &lt;span class="c"&gt;//if the random value is eq to 3 then fail&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c"&gt;//sleep to simulate work&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="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&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="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"job %d is done by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;WorkerID&lt;/span&gt;&lt;span class="o"&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;JobID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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="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;"worker "&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="s"&gt;" is done"&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;&lt;code&gt;stats&lt;/code&gt; is a &lt;code&gt;*Stats&lt;/code&gt; - &lt;strong&gt;one&lt;/strong&gt; struct, shared by all three workers. That is the&lt;br&gt;
whole point. If we passed &lt;code&gt;Stats&lt;/code&gt; by value, each worker would count its own jobs&lt;br&gt;
into its own copy and &lt;code&gt;main&lt;/code&gt; would print zeros.&lt;/p&gt;

&lt;p&gt;And in &lt;code&gt;main&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="n"&gt;stats&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;Stats&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;i&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;NumOfWorkers&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;Add&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="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&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;wg&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="n"&gt;initJobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;//using the clsoing go routines&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="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="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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="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;results&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result of job %d is done by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkerID&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Stats - Processed: %d, Failed: %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the last line reads &lt;code&gt;stats.JobProcessed&lt;/code&gt; &lt;strong&gt;without&lt;/strong&gt; locking. Is that a&lt;br&gt;
bug? No - and the reason is the closer goroutine we built in Part 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;workers write stats ──► wg.Done() ──► wg.Wait() ──► close(results)
                                                         │
                                                         ▼
                                      range results ends ──► safe read
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;for range results&lt;/code&gt; loop can only exit after &lt;code&gt;close(results)&lt;/code&gt;, which can only&lt;br&gt;
happen after &lt;code&gt;wg.Wait()&lt;/code&gt; returns, which can only happen after every worker has&lt;br&gt;
returned. By the time we print, there is no other goroutine left to race with.&lt;br&gt;
The Go memory model gives us that ordering for free. This is the same coordination&lt;br&gt;
we built in Part 1, now paying a second dividend.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 3: testing it
&lt;/h2&gt;

&lt;p&gt;Concurrency bugs do not show up reliably when you run the program by hand. They&lt;br&gt;
show up when you write tests that &lt;em&gt;try&lt;/em&gt; to break the code.&lt;/p&gt;
&lt;h3&gt;
  
  
  The unit tests
&lt;/h3&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;TestStats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;//testing stats struct methods&lt;/span&gt;
    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test processed method"&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;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stats&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;Stats&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processed&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;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected JobProcessed to be 1, got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&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="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test failed method"&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;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stats&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;Stats&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&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;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected JobFailed to be 1, got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&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;Boring, and that is fine. They pin down the single-goroutine behaviour so that&lt;br&gt;
when the concurrent test fails you know the arithmetic itself was never the&lt;br&gt;
problem.&lt;/p&gt;
&lt;h3&gt;
  
  
  The concurrent test - the one that matters
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Test concurrent access"&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;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stats&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;Stats&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&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="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;//waiting for two goroutines in each iteration&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="m"&gt;2&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;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;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processed&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;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;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failed&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="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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected JobProcessed to be 100, got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&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="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected JobFailed to be 100, got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&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;200 goroutines launched as fast as the runtime can spawn them, all hammering the&lt;br&gt;
same two bytes. If the locking is wrong, the counters land below 100.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wg.Add(2)&lt;/code&gt; sits &lt;strong&gt;before&lt;/strong&gt; the two &lt;code&gt;go&lt;/code&gt; statements, not inside them. Calling&lt;br&gt;
&lt;code&gt;Add&lt;/code&gt; inside the goroutine is a classic race: &lt;code&gt;wg.Wait()&lt;/code&gt; can return before the&lt;br&gt;
goroutine has even started counting itself.&lt;/p&gt;

&lt;p&gt;Also note the loop is &lt;code&gt;for range 100&lt;/code&gt; - a Go 1.22+ range-over-int, same as&lt;br&gt;
&lt;code&gt;for i := 0; i &amp;lt; 100; i++&lt;/code&gt; without the unused variable.&lt;/p&gt;
&lt;h3&gt;
  
  
  The end-to-end test
&lt;/h3&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;TestWorker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;jobs&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="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NumOfJobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;results&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="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NumOfJobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stats&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;Stats&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;wg&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="c"&gt;//testing with one worker&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="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&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="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&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;wg&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;i&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;NumOfJobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&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;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)}&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;jobs&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;Wait&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;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;//check if all jobs are processed&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;NumOfJobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expected %d jobs processed, got %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NumOfJobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&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;One detail that is easy to miss: &lt;code&gt;results&lt;/code&gt; is &lt;strong&gt;buffered&lt;/strong&gt; here&lt;br&gt;
(&lt;code&gt;make(chan Result, NumOfJobs)&lt;/code&gt;), while in &lt;code&gt;main&lt;/code&gt; it is unbuffered. In the test&lt;br&gt;
there is no consumer ranging over &lt;code&gt;results&lt;/code&gt;, so an unbuffered channel would block&lt;br&gt;
the worker on its first send and &lt;code&gt;wg.Wait()&lt;/code&gt; would hang forever - the exact&lt;br&gt;
deadlock from Part 1, rebuilt by accident inside a test. The buffer gives the&lt;br&gt;
worker somewhere to put its results so it can finish and call &lt;code&gt;wg.Done()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We assert on &lt;code&gt;JobProcessed&lt;/code&gt; only, not on &lt;code&gt;JobFailed&lt;/code&gt;, because failures are&lt;br&gt;
random (&lt;code&gt;rand.Intn(7) == 3&lt;/code&gt;). Every job is counted as processed regardless, so&lt;br&gt;
that number is deterministic. Asserting on random output is how you get a test&lt;br&gt;
that fails once a week and teaches the team to ignore it.&lt;/p&gt;

&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&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="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fmfxsfn5jd72e4phtn79k.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%2Fmfxsfn5jd72e4phtn79k.png" alt=" " width="350" height="29"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: &lt;code&gt;go test -race&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;A passing test proves the &lt;em&gt;result&lt;/em&gt; was right this time. It does not prove the&lt;br&gt;
program is &lt;em&gt;correct&lt;/em&gt;. Two goroutines can race and still, by luck, produce 100.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;-race&lt;/code&gt; answers the stronger question. It instruments every memory access and&lt;br&gt;
records which goroutine touched which address while holding which locks. If two&lt;br&gt;
goroutines touch the same address, at least one writes, and nothing orders them -&lt;br&gt;
it reports, even if the output happened to be correct.&lt;br&gt;
&lt;/p&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;-race&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fg70deuea0pc5kpwyf4ao.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%2Fg70deuea0pc5kpwyf4ao.png" alt=" " width="410" height="31"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Green. The mutex is doing its job.&lt;/p&gt;

&lt;p&gt;The race detector is not free - roughly 5-10x slower and much more memory - so it&lt;br&gt;
is a CI and development tool, not a production build flag. But &lt;strong&gt;every concurrent&lt;br&gt;
package you write should have at least one test that runs under &lt;code&gt;-race&lt;/code&gt;&lt;/strong&gt;, and&lt;br&gt;
that test needs enough concurrency to give the detector something to observe.&lt;br&gt;
That is why &lt;code&gt;TestStats/Test_concurrent_access&lt;/code&gt; exists.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5: &lt;code&gt;go test -bench&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Locks cost something. Let's measure it rather than guess.&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;BenchmarkStatsProcessed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;//sequential cost of one Lock/Unlock pair&lt;/span&gt;
    &lt;span class="n"&gt;stats&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;Stats&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;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Loop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processed&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;func&lt;/span&gt; &lt;span class="n"&gt;BenchmarkStatsProcessedParallel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;//same counter hammered from every available core -&amp;gt; lock contention&lt;/span&gt;
    &lt;span class="n"&gt;stats&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;Stats&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunParallel&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;pb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;testing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PB&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="n"&gt;pb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;stats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;processed&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;&lt;code&gt;b.Loop()&lt;/code&gt; is the Go 1.24+ form of the benchmark loop. It replaces&lt;br&gt;
&lt;code&gt;for i := 0; i &amp;lt; b.N; i++&lt;/code&gt; and keeps the compiler from optimizing away work whose&lt;br&gt;
result you never use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;b.RunParallel&lt;/code&gt; is the one that tells you something you did not already know: it&lt;br&gt;
runs the body on &lt;code&gt;GOMAXPROCS&lt;/code&gt; goroutines at once, so you are measuring &lt;strong&gt;lock&lt;br&gt;
contention&lt;/strong&gt;, not just lock overhead.&lt;br&gt;
&lt;/p&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; XXX &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;code&gt;-run XXX&lt;/code&gt; matches no test name, so only benchmarks run.)&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%2F6cj33a18e2xr1y0jzb0l.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%2F6cj33a18e2xr1y0jzb0l.png" alt=" " width="566" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On my M1 the two numbers are roughly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;BenchmarkStatsProcessed-8           54193095    22.13 ns/op
BenchmarkStatsProcessedParallel-8    8557812   157.4  ns/op
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uncontended, a lock/unlock pair is ~22 ns - cheap. Contended across 8 goroutines&lt;br&gt;
it is ~157 ns, about &lt;strong&gt;6.5x worse&lt;/strong&gt;, because the goroutines are now queuing behind&lt;br&gt;
each other and paying for cache-line ping-pong between cores.&lt;/p&gt;

&lt;p&gt;That is the real lesson of mutex performance: the lock itself is cheap, &lt;em&gt;waiting&lt;br&gt;
for it&lt;/em&gt; is not. Our workers hold the lock for one increment and spend seconds&lt;br&gt;
sleeping outside it, so contention is irrelevant here. If you ever find yourself&lt;br&gt;
doing I/O inside a critical section, this is the number that will bite you.&lt;/p&gt;

&lt;p&gt;(For a plain counter like this, &lt;code&gt;sync/atomic&lt;/code&gt; would be faster still - but that is for later chapter)&lt;/p&gt;
&lt;h2&gt;
  
  
  Now the fun part: what if we drop the mutex?
&lt;/h2&gt;

&lt;p&gt;Let's break it deliberately, the same way Part 1 broke on a deadlock. Comment out&lt;br&gt;
the locks:&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;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;processed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// s.mu.Lock()&lt;/span&gt;
    &lt;span class="c"&gt;// defer s.mu.Unlock()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobProcessed&lt;/span&gt;&lt;span class="o"&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;s&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Stats&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// s.mu.Lock()&lt;/span&gt;
    &lt;span class="c"&gt;// defer s.mu.Unlock()&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobFailed&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;h3&gt;
  
  
  Why this is broken
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;s.JobProcessed++&lt;/code&gt; looks atomic. It is not. On any real CPU it is three steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        read  JobProcessed   -&amp;gt;  register
        add   1              -&amp;gt;  register
        write register       -&amp;gt;  JobProcessed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two goroutines can interleave those steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; time ──────────────────────────────────────────────────►

 Worker 1   read(5) ──── add ──── write(6)
 Worker 2        read(5) ──── add ──── write(6)
                                          ▲
                                two increments, counter moved by 1
                                          = lost update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both read 5, both compute 6, both write 6. One increment vanished. This is a&lt;br&gt;
&lt;strong&gt;lost update&lt;/strong&gt;, and with 200 goroutines it does not happen once - it happens&lt;br&gt;
constantly.&lt;/p&gt;

&lt;p&gt;It gets worse than lost counts. Without synchronization there is no&lt;br&gt;
&lt;em&gt;happens-before&lt;/em&gt; relationship between the goroutines at all, so the Go memory&lt;br&gt;
model gives the compiler and the CPU permission to reorder and cache these&lt;br&gt;
accesses. A goroutine can keep &lt;code&gt;JobProcessed&lt;/code&gt; in a register and never publish it.&lt;br&gt;
The behaviour is undefined, not merely "slightly off" - which is exactly why you&lt;br&gt;
cannot debug this by adding &lt;code&gt;fmt.Println&lt;/code&gt; and staring at it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Running it
&lt;/h3&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="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This may well &lt;strong&gt;pass&lt;/strong&gt;. That is the trap. With only 100 iterations and a fast&lt;br&gt;
machine, the goroutines often serialize by accident and you get 100 and 100.&lt;br&gt;
Data races are not deterministic; a green test here proves nothing.&lt;/p&gt;

&lt;p&gt;Now the real check:&lt;br&gt;
&lt;/p&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;-race&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fr3b8fix0h6ujei4f8mnc.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%2Fr3b8fix0h6ujei4f8mnc.png" alt=" " width="745" height="203"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;==================
WARNING: DATA RACE
Read at 0x00c0000123d5 by goroutine 13:
  worker-pool.(*Stats).failed()
      .../worker-pool-series/golang/worker-pool-v2/main.go:46 +0x78
  worker-pool.TestStats.func3.2()
      .../worker-pool-series/golang/worker-pool-v2/main_test.go:52 +0x74

Previous write at 0x00c0000123d5 by goroutine 15:
  worker-pool.(*Stats).failed()
      .../worker-pool-series/golang/worker-pool-v2/main.go:46 +0x8c
  worker-pool.TestStats.func3.2()
      .../worker-pool-series/golang/worker-pool-v2/main_test.go:52 +0x74

Goroutine 13 (running) created at:
  worker-pool.TestStats.func3()
      .../worker-pool-series/golang/worker-pool-v2/main_test.go:50 +0x78
  testing.tRunner()
&lt;/span&gt;&lt;span class="c"&gt;...
&lt;/span&gt;&lt;span class="go"&gt;==================
--- FAIL: TestStats/Test_concurrent_access
    expected JobProcessed to be 100, got 97
FAIL
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How to read that report, because it is genuinely useful once you know the shape:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Read at 0x...&lt;/code&gt; / &lt;code&gt;Previous write at 0x...&lt;/code&gt;&lt;/strong&gt; - the same memory address, hit
by two different goroutines. Same address is the whole finding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The two stacks&lt;/strong&gt; - where each access happened. Here both point at
&lt;code&gt;main.go:42&lt;/code&gt;, the bare &lt;code&gt;s.JobFailed++&lt;/code&gt;. The detector puts your bug on a line
number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Goroutine N created at&lt;/code&gt;&lt;/strong&gt; - where each goroutine was spawned, so you can trace
it back to the &lt;code&gt;go&lt;/code&gt; statement in the test.&lt;/li&gt;
&lt;li&gt;Then the assertion failure underneath: &lt;code&gt;got 97&lt;/code&gt; instead of 100. Three lost
updates in a single run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put the two &lt;code&gt;s.mu&lt;/code&gt; lines back, rerun &lt;code&gt;go test -race .&lt;/code&gt;, and it is green again.&lt;br&gt;
That round trip - break it, see the report, fix it, see it clear - is the fastest&lt;br&gt;
way I know to build a real intuition for what a mutex is actually buying you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next
&lt;/h2&gt;

&lt;p&gt;In Part 3 we keep going through &lt;code&gt;sync&lt;/code&gt;: &lt;code&gt;sync/atomic&lt;/code&gt; for counters like this one&lt;br&gt;
(no lock at all), &lt;code&gt;sync.RWMutex&lt;/code&gt; for read-heavy state.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/aymane-belassiria/worker-pool-series/tree/main/golang/worker-pool-v2" rel="noopener noreferrer"&gt;https://github.com/aymane-belassiria/worker-pool-series/tree/main/golang/worker-pool-v2&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>concurrency</category>
      <category>learning</category>
    </item>
    <item>
      <title>Worker pool in Golang - part1</title>
      <dc:creator>aymane</dc:creator>
      <pubDate>Sun, 02 Aug 2026 14:14:34 +0000</pubDate>
      <link>https://dev.to/aymane-belassiria/worker-pool-in-golang-part1-5e60</link>
      <guid>https://dev.to/aymane-belassiria/worker-pool-in-golang-part1-5e60</guid>
      <description>&lt;h2&gt;
  
  
  Building a Worker Pool in Go
&lt;/h2&gt;

&lt;p&gt;In this series of articles, we'll explore one of the most widely used concurrency patterns in Go: the &lt;strong&gt;Worker Pool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We'll learn what a worker pool is, why it exists, and when you should use it. More importantly, we'll implement several versions using Go's concurrency primitives, compare different approaches, and discuss the trade-offs and performance characteristics of each implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Worker Pool?
&lt;/h2&gt;

&lt;p&gt;Between the 1960s and the 1980s, computer scientists developed techniques to efficiently manage the creation and scheduling of threads and processes. Since creating these resources is relatively expensive, they introduced the concept of &lt;strong&gt;worker pools&lt;/strong&gt; (also known as &lt;strong&gt;thread pools&lt;/strong&gt;), where a fixed number of workers are created in advance and reused to execute multiple tasks.&lt;/p&gt;

&lt;p&gt;Instead of continuously creating and destroying threads, applications reuse existing workers, significantly reducing overhead and improving performance for CPU-intensive and I/O-intensive workloads.&lt;/p&gt;

&lt;p&gt;Later, with the rise of web servers, developers faced the challenge of handling thousands of incoming client requests while making efficient use of the server's hardware resources. The worker pool pattern became a natural solution because it limits the amount of concurrent work while maximizing resource utilization.&lt;/p&gt;

&lt;p&gt;Today, worker pools remain one of the fundamental concurrency patterns. Almost every modern web server, runtime, and application framework uses some variation of this technique to efficiently serve requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let's Implement Our First Worker Pool in Go
&lt;/h2&gt;

&lt;p&gt;Now that we have a basic understanding of the worker pool pattern, it's time to build one in Go.&lt;/p&gt;

&lt;p&gt;We won't build anything fancy just yet—our goal is simply to understand the core idea and see how everything works behind the scenes.&lt;/p&gt;

&lt;p&gt;Our implementation will follow four simple steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a fixed number of workers.&lt;/li&gt;
&lt;li&gt;Send jobs through a channel.&lt;/li&gt;
&lt;li&gt;Let each worker process incoming jobs.&lt;/li&gt;
&lt;li&gt;Collect the results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By the end of this implementation, you'll have a solid understanding of how worker pools work internally. Later in this series, we'll extend it with graceful shutdown, context cancellation, error handling, dynamic worker scaling, and more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the &lt;code&gt;Job&lt;/code&gt; and &lt;code&gt;Result&lt;/code&gt; Types
&lt;/h2&gt;

&lt;p&gt;Let's begin by defining the data structures our worker pool will use.&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;Job&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;ID&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Result&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;WorkerID&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;JobID&lt;/span&gt;    &lt;span class="kt"&gt;byte&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Job&lt;/code&gt; represents a unit of work. In a real application, this could be a database query, an HTTP request, image processing, file compression, or any other task.&lt;/p&gt;

&lt;p&gt;For now, we'll keep it simple and only store an ID.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Result&lt;/code&gt; represents the output of a processed job. In real-world applications, it may also include processed data, errors, execution time, or any additional metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementing the Worker
&lt;/h2&gt;

&lt;p&gt;Now let's implement the heart of our worker pool: the worker function.&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;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="k"&gt;chan&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;results&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Result&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="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="c"&gt;// Process jobs until the channel is closed.&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&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;jobs&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Job %d is being processed by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Simulate some work.&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="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Intn&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="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&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;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Job %d completed by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;WorkerID&lt;/span&gt;&lt;span class="o"&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;JobID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Worker %d has stopped&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, there's nothing particularly complicated here.&lt;/p&gt;

&lt;p&gt;The worker receives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;its ID,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;receive-only&lt;/strong&gt; jobs channel,&lt;/li&gt;
&lt;li&gt;a &lt;strong&gt;send-only&lt;/strong&gt; results channel,&lt;/li&gt;
&lt;li&gt;and a &lt;code&gt;sync.WaitGroup&lt;/code&gt; used to notify the main goroutine when the worker exits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside the loop, the worker continuously receives jobs from the &lt;code&gt;jobs&lt;/code&gt; channel, processes them, and sends the corresponding &lt;code&gt;Result&lt;/code&gt; to the &lt;code&gt;results&lt;/code&gt; channel. Once the &lt;code&gt;jobs&lt;/code&gt; channel is closed, the loop exits and the worker terminates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initializing the Jobs
&lt;/h2&gt;

&lt;p&gt;Let's create a helper function that generates our jobs.&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;initJobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;chan&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Job&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="n"&gt;i&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;NumOfJobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&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;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// No more jobs.&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;jobs&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;This function simply sends &lt;code&gt;NumOfJobs&lt;/code&gt; jobs into the channel and closes it afterward so the workers know there is no more work to process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Assembling Everything
&lt;/h2&gt;

&lt;p&gt;Now let's wire everything together inside &lt;code&gt;main()&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="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;jobs&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="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NumOfJobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// buffered channel&lt;/span&gt;
    &lt;span class="n"&gt;results&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="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c"&gt;// unbuffered channel&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&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;NumOfWorkers&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;Add&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="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&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;wg&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="n"&gt;initJobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&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;Wait&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;results&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;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;results&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Job %d was processed by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkerID&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="s"&gt;"All jobs have been processed."&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;Everything looks correct at first glance.&lt;/p&gt;

&lt;p&gt;Let's run it.&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%2F7zk8h0mek1kw6dtrr8od.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%2F7zk8h0mek1kw6dtrr8od.png" alt="screen shot" width="388" height="104"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oops! We immediately hit a &lt;strong&gt;deadlock&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I intentionally kept this mistake because it's a great reminder that concurrent programming requires careful coordination. If you're not careful, you'll eventually run into problems such as deadlocks or race conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does the Deadlock Happen?
&lt;/h2&gt;

&lt;p&gt;Inside the worker, we send the result through an &lt;strong&gt;unbuffered&lt;/strong&gt; channel:&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;results&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;WorkerID&lt;/span&gt;&lt;span class="o"&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;JobID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sending on an unbuffered channel blocks until another goroutine receives the value.&lt;/p&gt;

&lt;p&gt;Meanwhile, the main goroutine is waiting 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="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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before it starts reading from the &lt;code&gt;results&lt;/code&gt; channel.&lt;/p&gt;

&lt;p&gt;This creates a circular dependency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every worker is blocked trying to send a result.&lt;/li&gt;
&lt;li&gt;The main goroutine is blocked waiting for every worker to finish.&lt;/li&gt;
&lt;li&gt;The workers cannot finish because they're blocked on the channel send.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since no goroutine can make progress, the Go runtime detects a deadlock.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the Deadlock
&lt;/h2&gt;

&lt;p&gt;Fortunately, the fix is simple.&lt;/p&gt;

&lt;p&gt;We'll introduce what I like to call a &lt;strong&gt;closer goroutine&lt;/strong&gt;. Its only responsibility is to wait until every worker finishes and then close the &lt;code&gt;results&lt;/code&gt; channel.&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;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="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="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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;Our final &lt;code&gt;main()&lt;/code&gt; function now looks like this:&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;jobs&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="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NumOfJobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// buffered channel&lt;/span&gt;
    &lt;span class="n"&gt;results&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="n"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c"&gt;// unbuffered channel&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&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;NumOfWorkers&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;Add&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="k"&gt;go&lt;/span&gt; &lt;span class="n"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&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;wg&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="n"&gt;initJobs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Closer goroutine.&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="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="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&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="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;results&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;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Job %d was processed by worker %d&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JobID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WorkerID&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="s"&gt;"All jobs have been processed."&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;Now the main goroutine immediately starts consuming results, allowing workers to continue sending values without blocking indefinitely. Once all workers exit, the closer goroutine closes the &lt;code&gt;results&lt;/code&gt; channel, which naturally terminates the &lt;code&gt;range&lt;/code&gt; loop.&lt;/p&gt;

&lt;p&gt;Another way to solve this problem is to start consuming the &lt;code&gt;results&lt;/code&gt; channel before calling &lt;code&gt;wg.Wait()&lt;/code&gt;. The important takeaway is that &lt;strong&gt;an unbuffered channel always requires a receiver&lt;/strong&gt;. Otherwise, every sender will eventually block.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For this first implementation, I intentionally kept everything simple so we could focus on understanding the worker pool pattern itself.&lt;/p&gt;

&lt;p&gt;Even with this small example, we've already covered several important Go concurrency concepts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Goroutines&lt;/li&gt;
&lt;li&gt;Channels&lt;/li&gt;
&lt;li&gt;Directional channels&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sync.WaitGroup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Closing channels&lt;/li&gt;
&lt;li&gt;Understanding and fixing deadlocks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next part of this series, we'll continue improving our worker pool by exploring more of the &lt;code&gt;sync&lt;/code&gt; package, writing proper tests with Go's &lt;code&gt;testing&lt;/code&gt; package, and benchmarking different implementations to better understand their performance characteristics.&lt;br&gt;
The complete source code for this article is available on GitHub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/aymane-belassiria/worker-pool-series/tree/main/golang/worker-pool-v1" rel="noopener noreferrer"&gt;https://github.com/aymane-belassiria/worker-pool-series/tree/main/golang/worker-pool-v1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>go</category>
      <category>design</category>
    </item>
  </channel>
</rss>
