<?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: GoyesDev</title>
    <description>The latest articles on DEV Community by GoyesDev (@david_goyes_a488f58a17a53).</description>
    <link>https://dev.to/david_goyes_a488f58a17a53</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%2F3646790%2F7fd821bf-6de1-467c-a6b6-0ffd90e943be.jpeg</url>
      <title>DEV Community: GoyesDev</title>
      <link>https://dev.to/david_goyes_a488f58a17a53</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/david_goyes_a488f58a17a53"/>
    <language>en</language>
    <item>
      <title>[GCD] Problemas de GCD</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 22:07:25 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-problemas-de-gcd-25og</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-problemas-de-gcd-25og</guid>
      <description>&lt;p&gt;GCD resolvió el problema que motivó toda esta serie de artículos: administrar hilos a mano es peligroso y difícil de acertar. Pero GCD trae sus propios problemas, que no desaparecen solo por usar colas en lugar de hilos directamente. Este artículo repasa siete de ellos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Complejidad conceptual: hilo y cola son dos cosas distintas
&lt;/h2&gt;

&lt;p&gt;Trabajar con GCD obliga a razonar sobre dos conceptos al mismo tiempo: la cola a la que se despacha trabajo, y el hilo en el que ese trabajo termina corriendo. No son lo mismo, y la relación entre ambos no siempre es obvia. Hace falta tener en cuenta, para cada tarea despachada:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Si la cola es serial o concurrente (cambia si hay garantía de exclusión mutua).&lt;/li&gt;
&lt;li&gt;Si se despacha con &lt;code&gt;sync&lt;/code&gt; o &lt;code&gt;async&lt;/code&gt; (cambia si el llamador espera, y en qué hilo puede terminar reutilizándose el trabajo).&lt;/li&gt;
&lt;li&gt;Qué QoS tiene la tarea (cambia con qué prioridad compite por CPU).&lt;/li&gt;
&lt;li&gt;Si el código ya está corriendo en esa misma cola (una llamada &lt;code&gt;sync&lt;/code&gt; reentrante produce un deadlock, cubierto en el artículo sobre DQ Serial: ejecución síncrona).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ese modelo mental hay que reconstruirlo en cada punto del código donde se despacha trabajo. Un error tan simple como llamar &lt;code&gt;DispatchQueue.main.sync&lt;/code&gt; desde código que ya corre en el hilo principal, sin darse cuenta de en qué cola se está parado, produce un deadlock silencioso que no aparece hasta que el código corre.&lt;/p&gt;

&lt;h2&gt;
  
  
  Completion handlers anidados
&lt;/h2&gt;

&lt;p&gt;Cuando una operación asíncrona depende del resultado de otra, y esa a su vez de otra, el código con closures de finalización se anida cada vez más hacia la derecha.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento encadena tres operaciones asíncronas, cada una dentro del closure de finalización de la anterior:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="kt"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;]?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="kt"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;]?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;firstPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;fetchComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;firstPost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comments&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con tres pasos ya es notorio. Con cinco o seis (una secuencia de llamadas de red real no es rara que llegue ahí), el código se vuelve difícil de leer en orden: cada nivel de indentación introduce su propio alcance, sus propias variables capturadas, y su propio punto de retorno anticipado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Manejo de errores doloroso
&lt;/h2&gt;

&lt;p&gt;Los closures de finalización no tienen una forma estándar de propagar errores hacia arriba, como sí la tiene &lt;code&gt;throws&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt;. Cada nivel de anidamiento tiene que manejar su propio error por separado, normalmente repitiendo el mismo patrón una y otra vez.&lt;/p&gt;

&lt;p&gt;Retomando el fragmento anterior, con manejo de errores explícito en cada nivel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Comment&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;fetchUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;userResult&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;userResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error obteniendo el usuario: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;fetchPosts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;postsResult&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;postsResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error obteniendo los posts: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;firstPost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;posts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;fetchComments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;firstPost&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;commentsResult&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
          &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;commentsResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error obteniendo los comentarios: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;comments&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comments&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;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;No hay ningún mecanismo del lenguaje que junte los tres &lt;code&gt;switch&lt;/code&gt; en un solo punto de manejo de errores. Cada nivel repite la misma estructura, y olvidar un caso en cualquiera de ellos compila sin advertencia.&lt;/p&gt;

&lt;h2&gt;
  
  
  Propenso a olvidar llamar el closure de callback
&lt;/h2&gt;

&lt;p&gt;El compilador no obliga a que un closure de finalización se llame en todos los caminos posibles de una función. Si una rama del código retorna sin invocar el &lt;code&gt;completion&lt;/code&gt;, el código que llamó a esa función se queda esperando una respuesta que nunca llega, sin ningún error ni advertencia.&lt;/p&gt;

&lt;p&gt;Este otro fragmento ilustra una función con una rama que olvida llamar a &lt;code&gt;completion&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;someCondition&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Nunca se llama completion(): quien llame a fetchValue() espera para siempre&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&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;Este es el mismo tipo de bug, en esencia, que olvidar &lt;code&gt;group.leave()&lt;/code&gt; en &lt;code&gt;DispatchGroup&lt;/code&gt; (cubierto en el artículo sobre DispatchGroup): el código compila perfectamente, y el problema solo se manifiesta en tiempo de ejecución, como un colgado que puede ser difícil de reproducir si &lt;code&gt;someCondition&lt;/code&gt; casi siempre es verdadera.&lt;/p&gt;

&lt;h2&gt;
  
  
  Carrera de datos sin protección del compilador
&lt;/h2&gt;

&lt;p&gt;El compilador de Swift no impide compilar código como el de &lt;code&gt;UnsafeCounter&lt;/code&gt; (artículo sobre DQ Concurrente: ejecución asíncrona), donde varias tareas modifican el mismo estado desde hilos distintos sin ninguna sincronización. GCD no tiene ningún mecanismo propio que detecte esto en tiempo de compilación: hace falta usar herramientas externas, como el Thread Sanitizer de Xcode, para encontrar estas carreras, y solo detectan las que efectivamente ocurren durante una ejecución concreta, no todas las que son posibles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Explosión de threads
&lt;/h2&gt;

&lt;p&gt;El pool de hilos que GCD administra para las colas concurrentes tiene un límite práctico, típicamente alrededor de 64 hilos por proceso para el pool por defecto. Cada tarea que se bloquea (con &lt;code&gt;sync&lt;/code&gt;, con &lt;code&gt;wait()&lt;/code&gt; de un &lt;code&gt;DispatchGroup&lt;/code&gt; o un &lt;code&gt;DispatchSemaphore&lt;/code&gt;, o durmiendo un hilo) sigue contando como un hilo ocupado desde la perspectiva del sistema operativo, aunque no esté haciendo trabajo útil. Si se despachan muchas tareas que se bloquean unas a otras sobre colas globales, GCD puede llegar a crear un hilo nuevo por cada una hasta agotar ese límite, y quedarse sin hilos disponibles para la tarea de la que las demás dependían. El resultado es indistinguible de un deadlock desde el punto de vista del usuario, aunque técnicamente el sistema sigue "funcionando": simplemente ya no tiene con qué avanzar.&lt;/p&gt;

&lt;p&gt;Este riesgo crece con patrones ya vistos en este módulo, como despachar una tarea bloqueante (&lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;wait()&lt;/code&gt;) dentro de otra tarea que ya está corriendo en una cola concurrente compartida: cada nivel de anidamiento consume un hilo más del pool mientras espera. La forma de evitarlo es limitar cuántas colas concurrentes propias existen en la app y evitar bloquear hilos del pool global innecesariamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inversión de prioridad
&lt;/h2&gt;

&lt;p&gt;Una tarea de prioridad alta puede terminar esperando por un recurso (un lock, un semáforo) que en ese momento tiene tomado una tarea de prioridad baja. Si el scheduler del sistema le da tiempo de CPU preferentemente a tareas de prioridad alta o media, y ninguna de esas tareas es la que tiene el recurso tomado, la tarea de baja prioridad que sí lo tiene puede tardar en recibir tiempo de CPU para terminar y liberarlo. El resultado es que la tarea de prioridad alta, a pesar de su prioridad, termina esperando indefinidamente por una tarea de prioridad baja que ni siquiera está compitiendo directamente con ella.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Estos siete problemas (más los ya cubiertos en artículos anteriores, como la ambigüedad entre orden de entrega y orden real de inicio) son la motivación para buscar herramientas de más alto nivel para expresar concurrencia, algo que queda fuera del alcance de este módulo sobre GCD.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gist.github.com/tclementdev/6af616354912b0347cdf6db159c37057" rel="noopener noreferrer"&gt;Making efficient use of the libdispatch (GCD) (tclementdev)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Priority_inversion" rel="noopener noreferrer"&gt;Priority inversion (Wikipedia)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Sincronización de hilos con DispatchSemaphore</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 21:49:17 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-sincronizacion-de-hilos-con-dispatchsemaphore-1ml7</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-sincronizacion-de-hilos-con-dispatchsemaphore-1ml7</guid>
      <description>&lt;p&gt;&lt;code&gt;DispatchBarrier&lt;/code&gt; resuelve el acceso exclusivo (una sola tarea a la vez) sobre una cola concurrente. &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchSemaphore&lt;/code&gt;&lt;/a&gt; generaliza esa idea: en lugar de limitar el acceso a una sola tarea, limita el acceso a un número arbitrario de tareas simultáneas, y no depende de ninguna cola en particular. También sirve, igual que &lt;code&gt;DispatchGroup&lt;/code&gt;, para convertir una API basada en callbacks en código que bloquea hasta obtener un resultado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué es un semáforo: wait() y signal()
&lt;/h2&gt;

&lt;p&gt;Un &lt;code&gt;DispatchSemaphore&lt;/code&gt; se crea con un contador inicial: &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/init(value:)" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchSemaphore(value:)&lt;/code&gt;&lt;/a&gt;. Ese contador representa cuántos accesos simultáneos están permitidos.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento crea un semáforo con un contador inicial de 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchSemaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/wait()" rel="noopener noreferrer"&gt;&lt;code&gt;wait()&lt;/code&gt;&lt;/a&gt; decrementa el contador. Si el resultado sigue siendo mayor o igual a cero, &lt;code&gt;wait()&lt;/code&gt; retorna de inmediato. Si el contador ya estaba en cero, &lt;code&gt;wait()&lt;/code&gt; bloquea el hilo que hace el llamado hasta que otro hilo llame &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/signal()" rel="noopener noreferrer"&gt;&lt;code&gt;signal()&lt;/code&gt;&lt;/a&gt;, que incrementa el contador y, si hay algún hilo bloqueado en &lt;code&gt;wait()&lt;/code&gt;, lo despierta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitar cuántas tareas acceden a un recurso al mismo tiempo
&lt;/h2&gt;

&lt;p&gt;Un semáforo con contador inicial &lt;code&gt;N&lt;/code&gt; permite que hasta &lt;code&gt;N&lt;/code&gt; tareas corran a la vez, y bloquea a las demás hasta que alguna de las &lt;code&gt;N&lt;/code&gt; termine y llame &lt;code&gt;signal()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El fragmento de abajo despacha cinco tareas sobre una cola concurrente, pero limita a dos el número de tareas que pueden correr al mismo tiempo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchSemaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.semaphore"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; empieza"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; termina"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&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;Aunque las cinco tareas se despachan de inmediato, solo dos pueden pasar de &lt;code&gt;semaphore.wait()&lt;/code&gt; a la vez. Las otras tres quedan bloqueadas en &lt;code&gt;wait()&lt;/code&gt; hasta que alguna de las dos primeras llame &lt;code&gt;signal()&lt;/code&gt;. &lt;code&gt;DispatchGroup&lt;/code&gt; aquí solo sirve para que el código de prueba espere a que las cinco tareas terminen, no forma parte del límite de concurrencia: eso lo impone el semáforo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Semáforo con contador 1: el mismo efecto que una barrera, sin cola dedicada
&lt;/h2&gt;

&lt;p&gt;Con contador inicial 1, un semáforo se comporta como un lock clásico: como máximo una tarea a la vez puede estar entre &lt;code&gt;wait()&lt;/code&gt; y &lt;code&gt;signal()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Este otro fragmento protege un contador compartido con un semáforo de contador 1, en lugar de usar &lt;code&gt;DispatchBarrier&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SafeCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchSemaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&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;La diferencia con &lt;code&gt;DispatchBarrier&lt;/code&gt; es que un semáforo no necesita una cola concurrente propia ni ninguna cola en particular: &lt;code&gt;wait()&lt;/code&gt; y &lt;code&gt;signal()&lt;/code&gt; funcionan sin importar desde qué hilo o cola se llamen. A cambio, a diferencia de &lt;code&gt;DispatchBarrier&lt;/code&gt; (que sigue permitiendo lecturas en paralelo mientras no haya una escritura de por medio), un semáforo con contador 1 serializa absolutamente todo acceso, tanto lecturas como escrituras, sin distinción.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convertir una API con callback en código que bloquea
&lt;/h2&gt;

&lt;p&gt;Igual que &lt;code&gt;DispatchGroup&lt;/code&gt;, un semáforo permite esperar de forma bloqueante a que termine un trabajo asíncrono con su propio closure de finalización, pero sin necesidad de &lt;code&gt;enter()&lt;/code&gt;/&lt;code&gt;leave()&lt;/code&gt;: basta con inicializar el semáforo en 0.&lt;/p&gt;

&lt;p&gt;Considérese la siguiente función, que simula una API asíncrona con su propio closure de finalización:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchRemoteValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&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;El siguiente fragmento usa un semáforo, inicializado en 0, para bloquear hasta que &lt;code&gt;fetchRemoteValue&lt;/code&gt; entregue su resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchSemaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

&lt;span class="n"&gt;fetchRemoteValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&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;value&lt;/span&gt;
  &lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&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="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Con contador inicial 0, la primera llamada a &lt;code&gt;wait()&lt;/code&gt; siempre bloquea, porque no hay ningún acceso disponible todavía. Solo se desbloquea cuando &lt;code&gt;fetchRemoteValue&lt;/code&gt; termina y su closure de finalización llama &lt;code&gt;signal()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  wait() en el hilo principal bloquea la interfaz
&lt;/h2&gt;

&lt;p&gt;Aunque parezca un detalle menor, &lt;code&gt;wait()&lt;/code&gt; bloquea el hilo que hace el llamado exactamente igual que &lt;code&gt;sync&lt;/code&gt;. Si ese hilo es el principal, la interfaz se congela mientras dura el bloqueo: no se procesan eventos de UI, no hay animaciones, la app deja de responder. Esto aplica sin importar cuán rápido se espere que el semáforo se libere: si &lt;code&gt;fetchRemoteValue&lt;/code&gt; tarda 300 milisegundos en el ejemplo anterior y ese &lt;code&gt;semaphore.wait()&lt;/code&gt; se llama desde el hilo principal, la interfaz queda congelada esos 300 milisegundos. En un caso real, donde la tarea que se espera es una llamada de red con latencia variable, ese bloqueo puede ser mucho más largo e impredecible. Por eso &lt;code&gt;wait()&lt;/code&gt; nunca debería llamarse desde el hilo principal cuando exista alguna posibilidad de que tarde en desbloquearse.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diferencia con DispatchGroup: signal() de más no falla de inmediato
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;DispatchGroup&lt;/code&gt; exige un balance estricto entre &lt;code&gt;enter()&lt;/code&gt; y &lt;code&gt;leave()&lt;/code&gt;: llamar &lt;code&gt;leave()&lt;/code&gt; de más produce un crash inmediato. Un semáforo es más permisivo: llamar &lt;code&gt;signal()&lt;/code&gt; sin un &lt;code&gt;wait()&lt;/code&gt; correspondiente no falla en ese momento, solo deja el contador por encima de su valor inicial, disponible para que futuros &lt;code&gt;wait()&lt;/code&gt; no bloqueen. El riesgo aparece más adelante: si el semáforo se libera de memoria mientras su contador no coincide con el valor inicial, libdispatch sí detecta ese estado inconsistente y termina la app con un crash (&lt;code&gt;BUG IN CLIENT OF LIBDISPATCH: Semaphore object deallocated while in use&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;El riesgo opuesto sigue siendo igual de real que con &lt;code&gt;DispatchGroup&lt;/code&gt;: si se llama &lt;code&gt;wait()&lt;/code&gt; más veces de las que se llama &lt;code&gt;signal()&lt;/code&gt;, el hilo que hace el llamado se queda bloqueado para siempre.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;semaphore&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchSemaphore&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Nunca se llama semaphore.signal(): este wait() se queda bloqueado para siempre&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver, juntando todo lo que se cubrió en este módulo, los problemas más comunes que aparecen al trabajar con GCD y cómo reconocerlos. Eso es lo que cubre el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore" rel="noopener noreferrer"&gt;DispatchSemaphore (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/init(value:)" rel="noopener noreferrer"&gt;init(value:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/wait()" rel="noopener noreferrer"&gt;wait() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchsemaphore/signal()" rel="noopener noreferrer"&gt;signal() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>swift</category>
      <category>ios</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Sincronización de hilos con DispatchBarrier</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 21:24:54 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-sincronizacion-de-hilos-con-dispatchbarrier-29pc</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-sincronizacion-de-hilos-con-dispatchbarrier-29pc</guid>
      <description>&lt;p&gt;En el artículo sobre DQ Concurrente: ejecución síncrona quedó pendiente un problema: en una cola concurrente, &lt;code&gt;sync&lt;/code&gt; no implica exclusión mutua. Una lectura despachada con &lt;code&gt;sync&lt;/code&gt; puede correr al mismo tiempo, en un hilo distinto, que una escritura despachada con &lt;code&gt;async&lt;/code&gt;, porque nada en la cola serializa el acceso entre tareas independientes. &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitemflags/barrier" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchBarrier&lt;/code&gt;&lt;/a&gt; resuelve exactamente ese problema, sin renunciar del todo al paralelismo de una cola concurrente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué hace una barrera
&lt;/h2&gt;

&lt;p&gt;Una tarea despachada con la bandera &lt;code&gt;.barrier&lt;/code&gt; sobre una cola concurrente se ejecuta con exclusión mutua: mientras corre, ninguna otra tarea de esa misma cola corre en paralelo con ella. Además, todas las tareas despachadas antes de la barrera tienen que terminar antes de que la barrera empiece, y todas las tareas despachadas después tienen que esperar a que la barrera termine. Efectivamente, la cola se comporta como serial mientras la barrera está en ejecución, y vuelve a comportarse como concurrente en cuanto termina.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo despachar una tarea con .barrier
&lt;/h2&gt;

&lt;p&gt;Tanto &lt;code&gt;sync&lt;/code&gt; como &lt;code&gt;async&lt;/code&gt; aceptan la bandera &lt;code&gt;.barrier&lt;/code&gt; a través del parámetro &lt;code&gt;flags:&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento despacha una tarea con &lt;code&gt;async(flags: .barrier)&lt;/code&gt; sobre una cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.barrier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Corriendo en exclusión mutua respecto al resto de la cola"&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;No tiene sentido aplicar &lt;code&gt;.barrier&lt;/code&gt; sobre una cola serial: una cola serial ya ejecuta una tarea a la vez, así que la exclusión mutua que ofrece la barrera ya existe de por sí.&lt;/p&gt;

&lt;h2&gt;
  
  
  Orden garantizado alrededor de la barrera
&lt;/h2&gt;

&lt;p&gt;El fragmento de abajo despacha dos tareas normales, luego una con &lt;code&gt;.barrier&lt;/code&gt;, y luego una tarea normal más, todas sobre la misma cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.barrier-order"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&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="nf"&gt;append&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="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="p"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// result empieza con 1 y 2 en algún orden, sigue con 3, y termina con 4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El orden entre &lt;code&gt;1&lt;/code&gt; y &lt;code&gt;2&lt;/code&gt; no está garantizado entre sí (son dos tareas normales corriendo en paralelo), pero ambas terminan antes de que &lt;code&gt;3&lt;/code&gt; (la barrera) pueda empezar. &lt;code&gt;4&lt;/code&gt;, despachada después de la barrera, no corre hasta que &lt;code&gt;3&lt;/code&gt; termina, así que siempre queda al final.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usar una barrera para proteger lecturas y escrituras
&lt;/h2&gt;

&lt;p&gt;El fragmento siguiente retoma la clase &lt;code&gt;UnsafeCache&lt;/code&gt; del artículo sobre DQ Concurrente: ejecución síncrona, protegida ahora con una barrera en la escritura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Cache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.cache-barrier"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;barrier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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;read&lt;/code&gt; sigue despachando con &lt;code&gt;sync&lt;/code&gt;, sin ninguna bandera especial: varias lecturas pueden seguir corriendo en paralelo entre sí, porque leer no modifica &lt;code&gt;storage&lt;/code&gt;. &lt;code&gt;write&lt;/code&gt; despacha con &lt;code&gt;async(flags: .barrier)&lt;/code&gt;: mientras una escritura está en curso, ninguna lectura ni ninguna otra escritura corre al mismo tiempo, así que ya no hay forma de leer un valor a medias ni de perder una escritura por una carrera de datos. Comparado con la versión completamente serial del primer &lt;code&gt;Cache&lt;/code&gt; (dos artículos atrás), esta sigue permitiendo que varias lecturas corran en paralelo cuando no hay ninguna escritura de por medio, algo que la versión serial no podía ofrecer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cuándo no usar una barrera
&lt;/h2&gt;

&lt;p&gt;Una barrera no debería aplicarse sobre una cola global (&lt;code&gt;DispatchQueue.global(qos:)&lt;/code&gt;). Las colas globales las administra el sistema y las comparte todo el proceso (y potencialmente otros procesos), así que despachar una barrera ahí bloquea trabajo de otras partes de la app que no tienen ninguna relación con lo que la barrera protege. Para usar una barrera, lo correcto es crear una cola concurrente propia, dedicada al recurso que se quiere proteger, como en el ejemplo de &lt;code&gt;Cache&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver &lt;code&gt;DispatchSemaphore&lt;/code&gt;, otra herramienta para limitar cuántas tareas acceden a un recurso al mismo tiempo, mostrado en un artículo posterior.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitemflags/barrier" rel="noopener noreferrer"&gt;DispatchWorkItemFlags.barrier (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;DispatchQueue (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Sincronización implícita y explícita con DispatchGroup</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 21:13:18 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-sincronizacion-implicita-y-explicita-con-dispatchgroup-451b</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-sincronizacion-implicita-y-explicita-con-dispatchgroup-451b</guid>
      <description>&lt;p&gt;En artículos anteriores ya apareció &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchGroup&lt;/code&gt;&lt;/a&gt;, pero solo como herramienta auxiliar para que el código de prueba esperara a que terminara trabajo despachado con &lt;code&gt;async&lt;/code&gt;. Este artículo lo cubre como tema central: cómo coordinar varias tareas independientes y ejecutar algo (o desbloquear un hilo) solo cuando todas hayan terminado, sin importar el orden en que terminen ni en qué cola corran.&lt;/p&gt;

&lt;h2&gt;
  
  
  Marcar entrada y salida de forma explícita: enter(), leave() y notify(queue:execute:)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/enter()" rel="noopener noreferrer"&gt;&lt;code&gt;group.enter()&lt;/code&gt;&lt;/a&gt; marca que una tarea empezó, y &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/leave()" rel="noopener noreferrer"&gt;&lt;code&gt;group.leave()&lt;/code&gt;&lt;/a&gt; marca que terminó. &lt;code&gt;enter()&lt;/code&gt; se llama antes de despachar la tarea; &lt;code&gt;leave()&lt;/code&gt; se llama dentro del closure despachado, en el punto donde el trabajo realmente termina.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde tres tareas se despachan con &lt;code&gt;async&lt;/code&gt; sobre una cola concurrente, cada una marcando su propio &lt;code&gt;enter()&lt;/code&gt;/&lt;code&gt;leave()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&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="nf"&gt;append&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="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Todas las tareas terminaron: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&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;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/notify(queue:execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;notify(queue:execute:)&lt;/code&gt;&lt;/a&gt; registra un closure que se ejecuta, en la cola indicada, cuando el número de &lt;code&gt;leave()&lt;/code&gt; recibidos iguala al número de &lt;code&gt;enter()&lt;/code&gt; registrados. No bloquea ningún hilo: el closure se encola automáticamente cuando el grupo se vacía. El orden en que las tres tareas terminan y anotan su valor en &lt;code&gt;result&lt;/code&gt; no está garantizado (la cola es concurrente), pero &lt;code&gt;notify&lt;/code&gt; solo corre después de que las tres, sin importar el orden, ya hayan llamado &lt;code&gt;leave()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Error común: llamar enter() dentro del bloque async
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;enter()&lt;/code&gt; tiene que ejecutarse en el hilo que despacha la tarea, antes de &lt;code&gt;queue.async&lt;/code&gt;, no dentro del closure que se despacha. Moverlo adentro es un error frecuente porque el código compila y parece razonable, pero rompe la sincronización.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento repite el ejemplo anterior, con ese error introducido:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.group-bug"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Debería llamarse antes de queue.async, no dentro&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&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="nf"&gt;append&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="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Todas terminaron: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Puede imprimir "[]", sin haber esperado a ninguna&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;group.notify&lt;/code&gt; se llama justo después del &lt;code&gt;for&lt;/code&gt;, en el hilo que hace el llamado, y en ese momento ninguna de las tres tareas ha alcanzado a ejecutar su &lt;code&gt;enter()&lt;/code&gt; todavía (recién se despacharon, corren en otro hilo). Desde la perspectiva del grupo, en ese instante no hay ningún &lt;code&gt;enter()&lt;/code&gt; pendiente: el conteo está en cero. &lt;code&gt;notify&lt;/code&gt; considera que el grupo ya está vacío y programa su closure para correr de inmediato, sin haber esperado a que ninguna tarea real termine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forma implícita: async(group:execute:)
&lt;/h2&gt;

&lt;p&gt;En lugar de &lt;code&gt;enter()&lt;/code&gt;/&lt;code&gt;leave()&lt;/code&gt; manuales, &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/async(group:execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;async(group:execute:)&lt;/code&gt;&lt;/a&gt; los maneja de forma implícita: &lt;code&gt;enter()&lt;/code&gt; ocurre justo antes de que el closure empiece a correr, y &lt;code&gt;leave()&lt;/code&gt; ocurre justo cuando ese mismo closure retorna.&lt;/p&gt;

&lt;p&gt;Cuando todo el trabajo de la tarea es código síncrono contenido por completo dentro del closure (sin llamadas asíncronas anidadas), esa forma implícita es segura y funciona exactamente igual que la forma explícita.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento reemplaza el &lt;code&gt;enter()&lt;/code&gt;/&lt;code&gt;leave()&lt;/code&gt; manual del primer ejemplo por la forma implícita, sin cambiar el comportamiento:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.group-implicit-safe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&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="nf"&gt;append&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="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Todas las tareas terminaron: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&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;Cada closure hace todo su trabajo (&lt;code&gt;Thread.sleep&lt;/code&gt; y &lt;code&gt;result.append(i)&lt;/code&gt;) antes de retornar, así que el &lt;code&gt;leave()&lt;/code&gt; implícito ocurre exactamente en el punto donde la tarea realmente termina. &lt;code&gt;group.notify&lt;/code&gt; sigue esperando correctamente a las tres.&lt;/p&gt;

&lt;h2&gt;
  
  
  El riesgo de async(group:execute:): callbacks anidados
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;leave()&lt;/code&gt; se dispara cuando el closure &lt;strong&gt;retorna&lt;/strong&gt;, no cuando el trabajo que ese closure representa termina de verdad. Para saber si &lt;code&gt;async(group:execute:)&lt;/code&gt; es seguro en un caso concreto, hay que revisar si el código que va dentro del closure tiene, a su vez, algún bloque de callback propio (una llamada de red, un temporizador, cualquier API que reciba un closure de finalización). Si lo tiene, ese callback interno es el verdadero punto donde el trabajo termina, y ahí es donde debería ir &lt;code&gt;leave()&lt;/code&gt;, no al final del closure exterior. Por eso &lt;code&gt;leave()&lt;/code&gt; no siempre va en la última línea del bloque: cuando hay un callback anidado, &lt;code&gt;leave()&lt;/code&gt; tiene que pasarse dentro de ese callback, y eso obliga a usar la forma explícita.&lt;/p&gt;

&lt;p&gt;Tomemos como ejemplo una función que simula una llamada asíncrona con su propio closure de finalización:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;fetchRemoteValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@escaping&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asyncAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;deadline&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&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;El siguiente fragmento usa &lt;code&gt;async(group:execute:)&lt;/code&gt; con esta función, de forma incorrecta:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.group-implicit-wrong"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fetchRemoteValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&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;value&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ El closure exterior ya retornó y leave() ya se llamó antes de que esto corra&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;describing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Puede imprimir "nil"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El closure exterior retorna de inmediato después de llamar a &lt;code&gt;fetchRemoteValue&lt;/code&gt;, porque &lt;code&gt;fetchRemoteValue&lt;/code&gt; en sí también retorna de inmediato (solo agenda el trabajo real). &lt;code&gt;async(group:execute:)&lt;/code&gt; marca &lt;code&gt;leave()&lt;/code&gt; en ese momento, antes de que el closure de finalización (&lt;code&gt;completion&lt;/code&gt;) siquiera haya corrido. La forma correcta es usar &lt;code&gt;enter()&lt;/code&gt;/&lt;code&gt;leave()&lt;/code&gt; explícitos, con &lt;code&gt;leave()&lt;/code&gt; dentro del callback real:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.group-implicit-correct"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;fetchRemoteValue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&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;value&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// El callback es el verdadero punto de finalización del trabajo&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"result: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;describing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Siempre tiene el valor 42&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sincronización bloqueante con wait()
&lt;/h2&gt;

&lt;p&gt;Además de &lt;code&gt;notify&lt;/code&gt;, que no bloquea nada, &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/wait()" rel="noopener noreferrer"&gt;&lt;code&gt;wait()&lt;/code&gt;&lt;/a&gt; bloquea el hilo que hace el llamado hasta que el grupo se vacía por completo.&lt;/p&gt;

&lt;p&gt;El fragmento de abajo despacha tres tareas sobre una cola concurrente y usa &lt;code&gt;wait()&lt;/code&gt; para bloquear hasta que las tres hayan terminado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.group-wait"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&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="nf"&gt;append&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="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Todas terminaron: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&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;print("Todas terminaron: \(result)")&lt;/code&gt; solo se ejecuta después de que &lt;code&gt;wait()&lt;/code&gt; retorna, y &lt;code&gt;wait()&lt;/code&gt; no retorna hasta que las tres tareas ya llamaron &lt;code&gt;leave()&lt;/code&gt;. Es la misma relación que existe entre &lt;code&gt;sync&lt;/code&gt; y &lt;code&gt;async&lt;/code&gt; al despachar una sola tarea: &lt;code&gt;notify&lt;/code&gt; es la versión no bloqueante, &lt;code&gt;wait()&lt;/code&gt; es la versión bloqueante.&lt;/p&gt;

&lt;h2&gt;
  
  
  Balance estricto entre enter() y leave()
&lt;/h2&gt;

&lt;p&gt;Cada &lt;code&gt;enter()&lt;/code&gt; tiene que tener su &lt;code&gt;leave()&lt;/code&gt; correspondiente, ni más ni menos. Los dos desbalances posibles fallan de formas distintas, como muestran los siguientes dos fragmentos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Más leave() que enter()
&lt;/h3&gt;

&lt;p&gt;Si se llama &lt;code&gt;leave()&lt;/code&gt; más veces que &lt;code&gt;enter()&lt;/code&gt;, el conteo interno del grupo baja por debajo de su valor inicial, lo cual es un error fatal detectado por libdispatch en tiempo de ejecución: la app termina con un crash del tipo &lt;code&gt;BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_group_leave()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento provoca ese crash llamando &lt;code&gt;leave()&lt;/code&gt; una vez de más:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Crash: BUG IN CLIENT OF LIBDISPATCH: Unbalanced call to dispatch_group_leave()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Más enter() que leave(): el grupo nunca se vacía
&lt;/h3&gt;

&lt;p&gt;Si se llama &lt;code&gt;enter()&lt;/code&gt; más veces que &lt;code&gt;leave()&lt;/code&gt;, el grupo nunca llega a un conteo de cero. &lt;code&gt;wait()&lt;/code&gt; se queda bloqueado para siempre y el closure de &lt;code&gt;notify&lt;/code&gt; nunca se ejecuta.&lt;/p&gt;

&lt;p&gt;Este otro fragmento ilustra ese caso, con una tarea que nunca llama a &lt;code&gt;leave()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ⚠️ Si esta tarea nunca llama a group.leave(), wait() y notify() se quedan esperando para siempre&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver cómo proteger el acceso a estado compartido en una cola concurrente sin serializar también las lecturas, con &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitemflags/barrier" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchBarrier&lt;/code&gt;&lt;/a&gt;. Eso es lo que cubre el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup" rel="noopener noreferrer"&gt;DispatchGroup (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/enter()" rel="noopener noreferrer"&gt;enter() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/leave()" rel="noopener noreferrer"&gt;leave() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/notify(queue:execute:)" rel="noopener noreferrer"&gt;notify(queue:execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchgroup/wait()" rel="noopener noreferrer"&gt;wait() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/async(group:execute:)" rel="noopener noreferrer"&gt;async(group:execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Cancelando un DispatchWorkItem</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 20:35:56 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-cancelando-un-dispatchworkitem-1hl5</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-cancelando-un-dispatchworkitem-1hl5</guid>
      <description>&lt;p&gt;Un &lt;code&gt;DispatchWorkItem&lt;/code&gt; se puede cancelar con &lt;code&gt;cancel()&lt;/code&gt;, pero la cancelación es cooperativa, no preventiva: &lt;code&gt;cancel()&lt;/code&gt; no interrumpe código que ya está corriendo. Lo que hace depende de en qué momento se llama, antes de que el trabajo empiece a correr o mientras ya está corriendo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancelar antes de que empiece a correr
&lt;/h2&gt;

&lt;p&gt;Si &lt;code&gt;cancel()&lt;/code&gt; se llama antes de que el &lt;code&gt;DispatchWorkItem&lt;/code&gt; empiece a ejecutarse, el bloque nunca llega a correr. GCD comprueba el estado de cancelación justo antes de invocar el bloque y, si ya está cancelado, se lo salta por completo.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se cancela un &lt;code&gt;DispatchWorkItem&lt;/code&gt; antes de despacharlo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.workitem-cancel"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;workItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Este mensaje nunca se imprime"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No importa que después se despache con &lt;code&gt;queue.async(execute: workItem)&lt;/code&gt;: como ya estaba cancelado antes de eso, el closure nunca se ejecuta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cancelar mientras ya está corriendo: cancelación cooperativa
&lt;/h2&gt;

&lt;p&gt;Si &lt;code&gt;cancel()&lt;/code&gt; se llama después de que el bloque ya empezó a correr, no lo detiene. Lo único que hace es marcar &lt;code&gt;isCancelled&lt;/code&gt; como &lt;code&gt;true&lt;/code&gt;. Detener el trabajo depende del bloque mismo: tiene que revisar &lt;code&gt;isCancelled&lt;/code&gt; en algún punto de su propia ejecución y decidir terminar antes de tiempo.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento muestra un &lt;code&gt;DispatchWorkItem&lt;/code&gt; que ejecuta un bucle y revisa &lt;code&gt;isCancelled&lt;/code&gt; en cada iteración:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;workItem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

&lt;span class="n"&gt;workItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;5&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;workItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isCancelled&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cancelado, deteniendo en la iteración &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Iteración &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&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;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.workitem-cancel"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&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;&lt;code&gt;workItem&lt;/code&gt; se declara como una variable opcional separada, en lugar de asignarse directamente al resultado de &lt;code&gt;DispatchWorkItem { ... }&lt;/code&gt;, porque el bloque necesita referenciar la variable &lt;code&gt;workItem&lt;/code&gt; para leer &lt;code&gt;isCancelled&lt;/code&gt;, y esa variable todavía no existe mientras se está construyendo el closure. Al llamar &lt;code&gt;cancel()&lt;/code&gt; después de 1.2 segundos, el bucle ya alcanzó a correr varias iteraciones (cada una tarda 0.5 segundos). &lt;code&gt;isCancelled&lt;/code&gt; pasa a &lt;code&gt;true&lt;/code&gt; en ese momento, pero la iteración que está en curso no se interrumpe: el bloque solo se entera y se detiene la próxima vez que su código revisa la propiedad, al principio de la siguiente iteración.&lt;/p&gt;

&lt;p&gt;Si el bloque nunca revisara &lt;code&gt;isCancelled&lt;/code&gt;, &lt;code&gt;cancel()&lt;/code&gt; marcaría la propiedad de todos modos, pero el bucle completaría sus 5 iteraciones sin que nada lo detuviera.&lt;/p&gt;

&lt;h2&gt;
  
  
  wait() sigue funcionando igual con un DispatchWorkItem cancelado
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;cancel()&lt;/code&gt; no afecta el comportamiento de &lt;code&gt;wait()&lt;/code&gt; ni de &lt;code&gt;notify(queue:execute:)&lt;/code&gt;. Si el trabajo se cancela antes de correr, &lt;code&gt;wait()&lt;/code&gt; retorna de inmediato porque no hay nada que ejecutar. Si se cancela mientras corre, &lt;code&gt;wait()&lt;/code&gt; sigue bloqueando hasta que el bloque termine por su cuenta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver cómo coordinar varias tareas que corren en paralelo y esperar a que todas terminen, con &lt;code&gt;DispatchGroup&lt;/code&gt;. Eso es lo que cubre el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem" rel="noopener noreferrer"&gt;DispatchWorkItem (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/cancel()" rel="noopener noreferrer"&gt;cancel() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/iscancelled" rel="noopener noreferrer"&gt;isCancelled (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Trabajando con DispatchWorkItem</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 20:20:47 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-trabajando-con-dispatchworkitem-in1</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-trabajando-con-dispatchworkitem-in1</guid>
      <description>&lt;p&gt;Hasta ahora, el trabajo despachado a una &lt;code&gt;DispatchQueue&lt;/code&gt; ha sido siempre un closure suelto: &lt;code&gt;queue.async { ... }&lt;/code&gt;. Un &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchWorkItem&lt;/code&gt;&lt;/a&gt; envuelve ese mismo bloque de código en un objeto, en lugar de pasarlo directamente. La diferencia práctica es que un closure suelto se pierde en el momento en que se despacha (no hay forma de referenciarlo después), mientras que un &lt;code&gt;DispatchWorkItem&lt;/code&gt; se puede guardar en una variable y usar para esperar su finalización, recibir una notificación cuando termine, o cancelarlo con &lt;code&gt;cancel()&lt;/code&gt;, tanto antes de que empiece a correr como mientras ya está corriendo (esto último se cubre en el artículo siguiente).&lt;/p&gt;

&lt;h2&gt;
  
  
  Creando y despachando un DispatchWorkItem
&lt;/h2&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se crea un &lt;code&gt;DispatchWorkItem&lt;/code&gt; y se despacha con &lt;code&gt;async&lt;/code&gt;, igual que se haría con un closure suelto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.workitem"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;workItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trabajo ejecutándose"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workItem&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;queue.async(execute:)&lt;/code&gt; y &lt;code&gt;queue.sync(execute:)&lt;/code&gt; aceptan un &lt;code&gt;DispatchWorkItem&lt;/code&gt; igual que aceptan un closure. Las reglas de &lt;code&gt;sync&lt;/code&gt; vs. &lt;code&gt;async&lt;/code&gt; y de cola serial vs. concurrente cubiertas en los artículos anteriores aplican exactamente igual: lo único que cambia es que ahora hay un objeto al que se puede volver después de despacharlo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejecutarlo directamente con perform()
&lt;/h2&gt;

&lt;p&gt;Un &lt;code&gt;DispatchWorkItem&lt;/code&gt; no necesita una cola para correr. &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/perform()" rel="noopener noreferrer"&gt;&lt;code&gt;perform()&lt;/code&gt;&lt;/a&gt; ejecuta el bloque de forma síncrona, en el hilo que hace el llamado, sin pasar por ninguna cola.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se llama &lt;code&gt;perform()&lt;/code&gt; directamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;workItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Corriendo en el hilo: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&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;workItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// Corre de inmediato, en el mismo hilo que llama a perform(). Equivale a invocar el closure directamente.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Esperar su finalización con wait()
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/wait()" rel="noopener noreferrer"&gt;&lt;code&gt;wait()&lt;/code&gt;&lt;/a&gt; bloquea el hilo que hace el llamado hasta que el &lt;code&gt;DispatchWorkItem&lt;/code&gt; termine de ejecutarse, sin importar en qué cola se haya despachado ni si se usó &lt;code&gt;sync&lt;/code&gt; o &lt;code&gt;async&lt;/code&gt; para despacharlo.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despacha un &lt;code&gt;DispatchWorkItem&lt;/code&gt; con &lt;code&gt;async&lt;/code&gt; y luego se espera su finalización por separado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.workitem"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;workItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trabajo terminado"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Despachado, sin esperar todavía"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Ahora sí terminó, un segundo después"&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;wait()&lt;/code&gt; es útil precisamente porque se llama en un punto distinto al del despacho: el código puede seguir haciendo otras cosas entre &lt;code&gt;queue.async(execute: workItem)&lt;/code&gt; y &lt;code&gt;workItem.wait()&lt;/code&gt;, algo que no es posible con &lt;code&gt;queue.sync&lt;/code&gt;, que bloquea de inmediato en el mismo punto donde se despacha.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recibir una notificación al terminar con notify(queue:execute:)
&lt;/h2&gt;

&lt;p&gt;En lugar de bloquear un hilo con &lt;code&gt;wait()&lt;/code&gt;, &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/notify(queue:execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;notify(queue:execute:)&lt;/code&gt;&lt;/a&gt; registra un closure que se ejecuta automáticamente, en la cola indicada, cuando el &lt;code&gt;DispatchWorkItem&lt;/code&gt; termina.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se registra una notificación antes de despachar el trabajo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.workitem"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;workItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchWorkItem&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Trabajo terminado"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;workItem&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&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="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Notificación: el trabajo ya terminó, en el hilo principal"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;workItem&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;notify&lt;/code&gt; no bloquea ningún hilo: el closure de notificación se encola en &lt;code&gt;.main&lt;/code&gt; (o en la cola que se indique) automáticamente cuando el trabajo termina, sin que nada tenga que esperar activamente por él.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Un &lt;code&gt;DispatchWorkItem&lt;/code&gt; también se puede cancelar con &lt;code&gt;cancel()&lt;/code&gt;, tanto antes de que empiece a correr (en ese caso nunca llega a ejecutarse) como mientras ya está corriendo (en ese caso queda marcado como cancelado en &lt;code&gt;isCancelled&lt;/code&gt;, pero el bloque debe revisar esa propiedad y detenerse por sí mismo). Eso es lo que cubre el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem" rel="noopener noreferrer"&gt;DispatchWorkItem (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/perform()" rel="noopener noreferrer"&gt;perform() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/wait()" rel="noopener noreferrer"&gt;wait() (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitem/notify(queue:execute:)" rel="noopener noreferrer"&gt;notify(queue:execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] DQ Concurrente: ejecución asíncrona</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Wed, 01 Jul 2026 20:02:27 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-concurrente-ejecucion-asincrona-3jk4</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-concurrente-ejecucion-asincrona-3jk4</guid>
      <description>&lt;p&gt;Esta es la combinación que produce paralelismo real en GCD: una cola concurrente despachando trabajo con &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/async(execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;async&lt;/code&gt;&lt;/a&gt;. El llamador no espera (por ser &lt;code&gt;async&lt;/code&gt;) y las tareas pueden correr al mismo tiempo en hilos distintos (por ser la cola concurrente).&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despachan tres tareas con &lt;code&gt;async&lt;/code&gt; sobre una cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concurrentQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.concurrente"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; empieza, hilo: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&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;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Las tres tareas ya se encolaron"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A diferencia de la cola serial con &lt;code&gt;async&lt;/code&gt; (donde el orden entre "Tarea 1", "Tarea 2" y "Tarea 3" sí estaba garantizado), aquí no hay ninguna garantía de orden entre esas tres líneas, ni entre ellas y "Las tres tareas ya se encolaron". El FIFO de la cola garantiza el orden en que GCD entrega las tres tareas para ejecución, pero una vez entregadas, cada una corre en un hilo independiente cuyo arranque real lo decide el scheduler del sistema operativo, no la cola.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reentrada: sigue siendo segura
&lt;/h2&gt;

&lt;p&gt;Igual que en la cola serial, llamar &lt;code&gt;async&lt;/code&gt; desde un closure que ya está corriendo en la misma cola concurrente no produce deadlock. &lt;code&gt;async&lt;/code&gt; nunca bloquea, así que solo agrega trabajo a la cola y continúa.&lt;/p&gt;

&lt;p&gt;Veamos qué pasa al llamar &lt;code&gt;async&lt;/code&gt; de forma anidada sobre la misma cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concurrentQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.concurrente"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea externa"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Seguro, no hay deadlock&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea interna"&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;A diferencia de lo que podría parecer, "Tarea externa" sí está garantizado que se imprime completa antes de que "Tarea interna" pueda empezar, y eso no depende de que la cola sea serial o concurrente. La razón es orden de programa: &lt;code&gt;print("Tarea externa")&lt;/code&gt; y la llamada anidada a &lt;code&gt;async&lt;/code&gt; son dos líneas secuenciales dentro del mismo closure, ejecutadas una después de la otra en el mismo hilo. La llamada anidada ni siquiera somete "Tarea interna" a la cola hasta que &lt;code&gt;print("Tarea externa")&lt;/code&gt; ya retornó, así que no hay ningún punto en el que ambas estén compitiendo por ejecutarse. La cola concurrente sí introduce una carrera real cuando varias tareas se someten de forma independiente, sin que una espere a que la otra termine de ejecutar su cuerpo completo: es el caso del ejemplo anterior con el &lt;code&gt;for&lt;/code&gt; y las tres tareas &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  El riesgo: nada serializa el acceso a estado compartido
&lt;/h2&gt;

&lt;p&gt;Combinar &lt;code&gt;async&lt;/code&gt; con una cola concurrente, sobre estado mutable compartido, produce una carrera de datos si no hay ningún otro mecanismo de sincronización de por medio.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento muestra una clase &lt;code&gt;UnsafeCounter&lt;/code&gt; que incrementa un contador compartido desde una cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;UnsafeCounter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.counter"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Lectura + suma + escritura no es una operación atómica&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;Cada llamado a &lt;code&gt;increment()&lt;/code&gt; retorna de inmediato y encola una escritura. Si varios hilos del pool concurrente ejecutan &lt;code&gt;self.value += 1&lt;/code&gt; al mismo tiempo, pueden leer el mismo valor antes de que el otro termine de escribir, y una de las dos sumas se pierde.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento llama &lt;code&gt;increment()&lt;/code&gt; 100 veces desde distintos hilos y luego lee el valor final a través de &lt;code&gt;currentValue()&lt;/code&gt;, un método que despacha la lectura con &lt;code&gt;sync&lt;/code&gt; sobre la misma cola para evitar leer a mitad de una escritura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UnsafeCounter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currentValue&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="c1"&gt;// No garantiza imprimir 100&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Llamar &lt;code&gt;increment()&lt;/code&gt; 100 veces desde distintos hilos no garantiza que &lt;code&gt;value&lt;/code&gt; termine en 100.&lt;/p&gt;

&lt;p&gt;Esta es la situación exacta que &lt;code&gt;DispatchBarrier&lt;/code&gt; y &lt;code&gt;DispatchSemaphore&lt;/code&gt; existen para resolver, y que se cubre en los siguientes artículos.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver &lt;code&gt;DispatchWorkItem&lt;/code&gt;, una forma de empaquetar el trabajo que se despacha en un objeto en lugar de un closure suelto. Es útil cuando se necesita cancelar ese trabajo o esperar a que termine desde otro punto del código. Después de eso, &lt;code&gt;DispatchGroup&lt;/code&gt;, &lt;code&gt;DispatchBarrier&lt;/code&gt; y &lt;code&gt;DispatchSemaphore&lt;/code&gt; cubren cómo coordinar y proteger trabajo concurrente como el de este artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/async(execute:)" rel="noopener noreferrer"&gt;async(execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;DispatchQueue (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] DQ Concurrente: ejecución síncrona</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 23:29:32 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-concurrente-ejecucion-sincrona-40id</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-concurrente-ejecucion-sincrona-40id</guid>
      <description>&lt;h1&gt;
  
  
  [GCD] DQ Concurrente: ejecución síncrona
&lt;/h1&gt;

&lt;p&gt;La API es la misma que en una cola serial: &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/a&gt; bloquea al llamador hasta que el closure despachado termina. Lo que cambia es qué hace la cola con el resto de su trabajo mientras tanto.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despacha un bloque con &lt;code&gt;sync&lt;/code&gt; sobre una cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concurrentQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.concurrent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// result == [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para una sola tarea despachada con &lt;code&gt;sync&lt;/code&gt;, el comportamiento observado es idéntico al de una cola serial: el llamador espera, y el orden queda garantizado: &lt;code&gt;result&lt;/code&gt; termina en &lt;code&gt;[1, 2, 3]&lt;/code&gt;, nunca en otro orden. La diferencia aparece cuando hay más tareas además de la que se está esperando.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;sync&lt;/code&gt; bloquea por esa tarea, no por la cola
&lt;/h2&gt;

&lt;p&gt;En una cola concurrente, varias tareas pueden estar corriendo en hilos distintos al mismo tiempo. &lt;code&gt;sync&lt;/code&gt; bloquea al llamador hasta que &lt;strong&gt;su&lt;/strong&gt; tarea termine. No detiene ni espera a las demás tareas que la cola esté procesando en paralelo.&lt;/p&gt;

&lt;p&gt;El siguiente fragmento despacha tres tareas con &lt;code&gt;async&lt;/code&gt; y luego una más con &lt;code&gt;sync&lt;/code&gt; sobre la misma cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concurrentQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.concurrente"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; empieza, hilo: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; termina"&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;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea de sincronización"&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;Las tres tareas despachadas con &lt;code&gt;async&lt;/code&gt; se despachan, en orden de código, antes que la "Tarea de sincronización". El &lt;code&gt;sync&lt;/code&gt; final espera a que su propio closure termine, pero no espera a que las tres tareas anteriores terminen. Esas siguen corriendo en paralelo, en sus propios hilos, independientemente de lo que haga el llamador.&lt;/p&gt;

&lt;h3&gt;
  
  
  El orden de entrega FIFO no garantiza el orden real de inicio
&lt;/h3&gt;

&lt;p&gt;Al correr exactamente este código, una salida real fue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tarea de sincronización
Tarea 2 empieza, hilo: &amp;lt;NSThread: 0x104b29bc0&amp;gt;{number = 7, name = (null)}
Tarea 3 empieza, hilo: &amp;lt;NSThread: 0x1048e06c0&amp;gt;{number = 3, name = (null)}
Tarea 1 empieza, hilo: &amp;lt;NSThread: 0x1048e0b80&amp;gt;{number = 5, name = (null)}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;"Tarea de sincronización", despachada &lt;em&gt;después&lt;/em&gt; de las tres, imprimió primero. Y entre las tres tareas &lt;code&gt;async&lt;/code&gt;, el orden tampoco fue 1, 2, 3, sino 2, 3, 1.&lt;/p&gt;

&lt;p&gt;Esto no contradice el FIFO de la cola: el FIFO garantiza el orden en que GCD &lt;strong&gt;entrega&lt;/strong&gt; los closures para ejecución, no el orden real en que cada hilo ejecuta su primera instrucción. Una vez entregado un closure a un hilo, es el scheduler del sistema operativo quien decide cuándo ese hilo corre, no GCD. Y &lt;code&gt;sync&lt;/code&gt;, a diferencia de &lt;code&gt;async&lt;/code&gt;, puede reutilizar el hilo que hace el llamado en lugar de pedir uno nuevo al pool concurrente: no tiene que pagar el costo de adquisición de un hilo nuevo, que sí pagan las tres tareas despachadas con &lt;code&gt;async&lt;/code&gt;. Por eso terminó primero pese a haberse despachado después.&lt;/p&gt;

&lt;h3&gt;
  
  
  Esa ventaja no garantiza que &lt;code&gt;sync&lt;/code&gt; siempre termine primero
&lt;/h3&gt;

&lt;p&gt;Reutilizar el hilo invocador solo elimina el costo de adquisición de un hilo nuevo. No dice nada sobre cuánto tarda el propio trabajo del bloque. Si el bloque despachado con &lt;code&gt;sync&lt;/code&gt; hace más trabajo que las tareas &lt;code&gt;async&lt;/code&gt;, termina después de ellas, sin importar que se haya ahorrado ese costo de adquisición.&lt;/p&gt;

&lt;p&gt;A continuación se modifica el ejemplo anterior: el bloque de &lt;code&gt;sync&lt;/code&gt; ahora duerme dos segundos antes de imprimir, mientras las tres tareas &lt;code&gt;async&lt;/code&gt; conservan su segundo de espera:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concurrentQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.concurrente"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; empieza"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; termina"&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;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea de sincronización"&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;Al correr exactamente este código, una salida real fue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Tarea 1 empieza
Tarea 2 empieza
Tarea 3 empieza
Tarea 1 termina
Tarea 2 termina
Tarea 3 termina
Tarea de sincronización
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El orden entre las tres tareas &lt;code&gt;async&lt;/code&gt; puede variar entre ejecuciones, como ya se mostró arriba. Lo que no varía es que las tres terminan, empiezan y terminan, antes que la tarea de sincronización imprima su línea.&lt;/p&gt;

&lt;p&gt;El orden de entrega FIFO sigue siendo el mismo: las tres tareas &lt;code&gt;async&lt;/code&gt; se entregan antes que el bloque &lt;code&gt;sync&lt;/code&gt;, sin excepción. Lo que cambió frente al ejemplo anterior es cuánto tarda cada tarea en completar su propio trabajo una vez que arranca: cada &lt;code&gt;async&lt;/code&gt; duerme un segundo, mientras que &lt;code&gt;sync&lt;/code&gt; duerme dos. La ventaja de reutilizar el hilo invocador evita el costo de adquisición, pero no compensa una diferencia de un segundo completo en el trabajo del bloque.&lt;/p&gt;

&lt;p&gt;Esto muestra que la ventaja de &lt;code&gt;sync&lt;/code&gt; sobre &lt;code&gt;async&lt;/code&gt; es evitar un costo de arranque, no una garantía de terminar primero. &lt;code&gt;sync&lt;/code&gt; termina primero solo cuando, sumando ese ahorro más el tiempo de su propio trabajo, sigue siendo más rápido que las alternativas. Si su trabajo tarda más, pierde esa carrera, sin importar que haya arrancado con ventaja.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encadenar varios &lt;code&gt;sync&lt;/code&gt; sí produce orden determinista
&lt;/h2&gt;

&lt;p&gt;Si en lugar de mezclar &lt;code&gt;sync&lt;/code&gt; con tareas &lt;code&gt;async&lt;/code&gt; independientes se despachan varias llamadas &lt;code&gt;sync&lt;/code&gt; seguidas sobre la misma cola concurrente, el resultado es tan predecible como en una cola serial: cada &lt;code&gt;sync&lt;/code&gt; bloquea por completo antes de que el código siga a la siguiente línea, así que nunca hay dos llamados &lt;code&gt;sync&lt;/code&gt; compitiendo entre sí por arrancar.&lt;/p&gt;

&lt;p&gt;Este otro fragmento encadena tres llamadas &lt;code&gt;sync&lt;/code&gt; seguidas sobre una cola concurrente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;concurrentQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.concurrent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;concurrentQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&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="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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// result == [1, 2, 3, 4, 5]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esto no contradice lo mostrado arriba: la falta de garantía de orden real de inicio aparece cuando varias tareas se despachan de forma independiente y pueden competir por arrancar en paralelo. Aquí no hay competencia posible, porque el código nunca llega a despachar la siguiente tarea hasta que la anterior ya terminó. Que la cola sea concurrente es irrelevante para este patrón de uso específico.&lt;/p&gt;

&lt;h2&gt;
  
  
  El riesgo: &lt;code&gt;sync&lt;/code&gt; no implica exclusión mutua
&lt;/h2&gt;

&lt;p&gt;Como una cola concurrente no serializa el acceso, usar &lt;code&gt;sync&lt;/code&gt; para leer un recurso no protege contra una escritura que se esté ejecutando en paralelo en otro hilo de la misma cola.&lt;/p&gt;

&lt;p&gt;El fragmento de abajo muestra una clase &lt;code&gt;UnsafeCache&lt;/code&gt; que usa una cola concurrente sin ningún mecanismo adicional de sincronización:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;UnsafeCache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.cache-concurrente"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Puede correr al mismo tiempo que una lectura&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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;A diferencia del &lt;code&gt;Cache&lt;/code&gt; con cola serial del artículo anterior, aquí &lt;code&gt;read&lt;/code&gt; y &lt;code&gt;write&lt;/code&gt; pueden ejecutarse en paralelo, en hilos distintos, sobre el mismo diccionario. Esto es justamente una carrera de datos. El FIFO de la cola solo garantiza el orden en que las tareas se &lt;strong&gt;entregan&lt;/strong&gt; para ejecución, no que una tarea espere a que la anterior termine antes de arrancar, y mucho menos que se ejecuten en exclusión mutua entre sí. Proteger el acceso en este escenario requiere &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchworkitemflags/barrier" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchBarrier&lt;/code&gt;&lt;/a&gt;, cubierto en un artículo posterior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver el mismo tipo de cola concurrente, pero despachando con &lt;code&gt;async&lt;/code&gt; en lugar de &lt;code&gt;sync&lt;/code&gt;. Ese es el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)" rel="noopener noreferrer"&gt;sync(execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;DispatchQueue (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] DQ Serial: ejecución asíncrona</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 23:08:55 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-serial-ejecucion-asincrona-1gk1</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-serial-ejecucion-asincrona-1gk1</guid>
      <description>&lt;p&gt;Despachar un closure con &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/async(execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;async&lt;/code&gt;&lt;/a&gt; lo encola y retorna de inmediato. El hilo que hizo el llamado no espera a que el closure se ejecute. Sigue corriendo el código que viene después sin bloquearse.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despacha un bloque con &lt;code&gt;async&lt;/code&gt; sobre una cola serial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;serialQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.serial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&lt;/span&gt;&lt;span class="p"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// result == [1, 3, 2]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;DispatchGroup&lt;/code&gt; se cubre en detalle en un artículo posterior. Aquí solo hace falta saber que &lt;code&gt;group.wait()&lt;/code&gt; bloquea hasta que todo lo que entró con &lt;code&gt;group.enter()&lt;/code&gt; haya salido con &lt;code&gt;group.leave()&lt;/code&gt;, y se usa únicamente para poder comprobar el resultado al final, no como parte de lo que se está demostrando.&lt;/p&gt;

&lt;p&gt;Lo único garantizado en este código es que &lt;code&gt;result.append(1)&lt;/code&gt; ocurre antes que cualquiera de los otros dos: es código síncrono, anterior al llamado a &lt;code&gt;async&lt;/code&gt;. El orden entre &lt;code&gt;result.append(3)&lt;/code&gt; y &lt;code&gt;result.append(2)&lt;/code&gt; no está garantizado por la API: &lt;code&gt;async&lt;/code&gt; retorna de inmediato, así que &lt;code&gt;result.append(3)&lt;/code&gt; casi siempre se ejecuta primero, y por eso el resultado es &lt;code&gt;[1, 3, 2]&lt;/code&gt; y no &lt;code&gt;[1, 2, 3]&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  La cola sigue siendo serial: el orden entre tareas sí está garantizado
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;async&lt;/code&gt; cambia si el llamador espera, no cambia cómo procesa la cola las tareas. Una cola serial sigue ejecutando una tarea a la vez, en el orden en que se despacharon (FIFO), sin importar si se despacharon con &lt;code&gt;sync&lt;/code&gt; o &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despachan tres bloques con &lt;code&gt;async&lt;/code&gt; sobre la misma cola serial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;serialQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.serial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchGroup&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&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="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;leave&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;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// result == [1, 5, 2, 3, 4]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;result.append(5)&lt;/code&gt; se ejecuta de inmediato, antes de que cualquiera de las tres tareas asíncronas alcance a correr (cada una duerme un segundo completo antes de anotar su número). Pero el orden &lt;strong&gt;entre&lt;/strong&gt; &lt;code&gt;2&lt;/code&gt;, &lt;code&gt;3&lt;/code&gt; y &lt;code&gt;4&lt;/code&gt; sí está garantizado por el FIFO de la cola serial: corren una a la vez, en el orden en que se despacharon, sin importar que ninguna haya bloqueado al llamador. Por eso no es necesario sincronizar la escritura sobre &lt;code&gt;result&lt;/code&gt; entre las tres tareas: al ser una cola serial, nunca corren en paralelo entre sí, así que no se accede a &lt;code&gt;result&lt;/code&gt; de forma concurrente en ningún momento.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;async&lt;/code&gt; reentrante no produce deadlock
&lt;/h2&gt;

&lt;p&gt;A diferencia de &lt;code&gt;sync&lt;/code&gt;, llamar &lt;code&gt;async&lt;/code&gt; sobre la misma cola serial en la que el código ya se está ejecutando es seguro. &lt;code&gt;async&lt;/code&gt; nunca bloquea, así que solo agrega el closure al final de la cola y continúa.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se llama &lt;code&gt;async&lt;/code&gt; de forma anidada sobre la misma cola:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;serialQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.serial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// Seguro, no hay deadlock&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea B"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"A sigue ejecutando"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Tarea A&lt;/span&gt;
&lt;span class="c1"&gt;// A sigue ejecutando&lt;/span&gt;
&lt;span class="c1"&gt;// Tarea B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;La tarea A termina de ejecutarse por completo (incluyendo el &lt;code&gt;print&lt;/code&gt; después del &lt;code&gt;async&lt;/code&gt; anidado) antes de que la cola pase a la tarea B, porque B se encoló al final, detrás de lo que faltaba de A.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué &lt;code&gt;Cache.write&lt;/code&gt; usa &lt;code&gt;async&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;En el &lt;a href="https://dev.to/david_goyes_a488f58a17a53/gcd-dq-serial-ejecucion-sincrona-y-sincronizacion-de-hilos-15o2"&gt;artículo anterior&lt;/a&gt;, &lt;code&gt;write(_:forKey:)&lt;/code&gt; despacha con &lt;code&gt;async&lt;/code&gt; precisamente por esto: quien escribe en la caché no necesita esperar a que la escritura se aplique para seguir ejecutando. Lo único que importa es que, cuando se llegue a leer con &lt;code&gt;sync&lt;/code&gt;, todas las escrituras encoladas antes ya se hayan procesado. Eso lo garantiza el FIFO de la cola serial, no el hecho de que el llamador haya esperado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver qué cambia cuando la cola, en lugar de serial, es concurrente: varias tareas pueden correr al mismo tiempo, y el FIFO deja de implicar "una a la vez". Eso es lo que cubren los siguientes dos artículos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/async(execute:)" rel="noopener noreferrer"&gt;async(execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;DispatchQueue (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] DQ Serial: ejecución síncrona y sincronización de hilos</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 22:59:50 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-serial-ejecucion-sincrona-y-sincronizacion-de-hilos-15o2</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-dq-serial-ejecucion-sincrona-y-sincronizacion-de-hilos-15o2</guid>
      <description>&lt;p&gt;Despachar un closure con &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)" rel="noopener noreferrer"&gt;&lt;code&gt;sync&lt;/code&gt;&lt;/a&gt; bloquea el hilo que hace el llamado hasta que ese closure termina de ejecutarse. El control no regresa al código que llamó a &lt;code&gt;sync&lt;/code&gt; hasta que el trabajo encolado se completó.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despacha un bloque con &lt;code&gt;sync&lt;/code&gt; sobre una cola serial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;serialQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.serial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// result == [1, 2, 3]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;result.append(3)&lt;/code&gt; solo se ejecuta después de que el closure despachado con &lt;code&gt;sync&lt;/code&gt; termina, incluyendo el segundo completo de &lt;code&gt;Thread.sleep&lt;/code&gt;. Si &lt;code&gt;sync&lt;/code&gt; no bloqueara, &lt;code&gt;result&lt;/code&gt; sería &lt;code&gt;[1, 3, 2]&lt;/code&gt; (el mismo resultado que se obtiene al despachar con &lt;code&gt;async&lt;/code&gt;, cubierto en el artículo siguiente).&lt;/p&gt;

&lt;p&gt;En una cola serial, &lt;code&gt;sync&lt;/code&gt; se combina con dos propiedades ya cubiertas: el FIFO (las tareas empiezan en el orden en que se despachan) y la exclusión mutua de una cola serial (solo una tarea corre a la vez). El resultado es que &lt;code&gt;sync&lt;/code&gt; no solo espera a que termine el closure despachado: espera a que termine después de que la cola haya procesado todo lo que estaba encolado antes.&lt;/p&gt;

&lt;p&gt;Esto se sostiene incluso encadenando varias llamadas &lt;code&gt;sync&lt;/code&gt; seguidas: cada una bloquea por completo antes de que el código que llama alcance la siguiente línea, así que se ejecutan una detrás de otra, en orden estricto.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se encadenan tres llamadas &lt;code&gt;sync&lt;/code&gt; seguidas sobre la misma cola serial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;serialQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.gcd.serial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&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="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// result == [1, 2, 3, 4, 5] (cada sync espera su segundo completo antes de que el código siga a la siguiente línea)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  El error más común: deadlock por reentrada
&lt;/h2&gt;

&lt;p&gt;Llamar &lt;code&gt;sync&lt;/code&gt; sobre la misma cola serial en la que el código ya se está ejecutando produce un deadlock.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se llama &lt;code&gt;sync&lt;/code&gt; de forma anidada sobre la misma cola:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;serialQueue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.serial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea A"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;serialQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Deadlock&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Tarea B"&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;La tarea A está corriendo dentro de la cola serial y bloquea ese hilo esperando a que la tarea B termine. Pero la tarea B no puede empezar: la cola es serial, solo procesa una tarea a la vez, y esa tarea (A) todavía no ha terminado: está esperando a B. Ninguna de las dos puede avanzar.&lt;/p&gt;

&lt;h3&gt;
  
  
  La versión real de este bug: &lt;code&gt;DispatchQueue.main.sync&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;DispatchQueue.main&lt;/code&gt; es, para este propósito, una cola serial cuyo único hilo de ejecución es el hilo principal. Cualquier código que ya esté corriendo en el hilo principal (un método del ciclo de vida de UIKit como &lt;code&gt;viewDidLoad()&lt;/code&gt;, una acción de botón, cualquier callback que llegue por el hilo principal) está, en términos de la cola, en la misma situación que la tarea A del ejemplo anterior.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se llama &lt;code&gt;DispatchQueue.main.sync&lt;/code&gt; desde &lt;code&gt;viewDidLoad()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SomeViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// ⚠️ Deadlock&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Esto nunca se imprime"&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;viewDidLoad()&lt;/code&gt; corre en el hilo principal. Al llamar &lt;code&gt;DispatchQueue.main.sync&lt;/code&gt;, ese hilo se bloquea esperando a que el closure se ejecute, pero el único hilo donde la cola &lt;code&gt;main&lt;/code&gt; puede ejecutar algo es justamente el que está bloqueado. El resultado es el mismo deadlock del ejemplo anterior, solo que disfrazado: no hace falta anidar &lt;code&gt;sync&lt;/code&gt; explícitamente para reproducirlo, basta con llamar &lt;code&gt;DispatchQueue.main.sync&lt;/code&gt; desde cualquier punto que ya esté corriendo en el hilo principal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usar &lt;code&gt;sync&lt;/code&gt; como mecanismo de sincronización
&lt;/h2&gt;

&lt;p&gt;La combinación de FIFO + exclusión mutua + bloqueo del llamador permite usar una cola serial para proteger el acceso a un recurso compartido, sin locks explícitos.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde una clase &lt;code&gt;Cache&lt;/code&gt; usa una cola serial para sincronizar lecturas y escrituras:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;Cache&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;queue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.cache"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[:]&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forKey&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forKey&lt;/span&gt; &lt;span class="nv"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&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;write&lt;/code&gt; despacha con &lt;code&gt;async&lt;/code&gt;: no bloquea al llamador, solo encola la escritura. &lt;code&gt;read&lt;/code&gt; despacha con &lt;code&gt;sync&lt;/code&gt;: bloquea al llamador hasta que la lectura se ejecute, y por el FIFO de la cola, esa lectura solo corre después de que todas las escrituras encoladas antes ya se aplicaron. El resultado es lectura siempre consistente con el estado más reciente que se haya encolado, sin declarar ningún lock.&lt;/p&gt;

&lt;p&gt;El costo de este enfoque es que las lecturas tampoco corren en paralelo entre sí: la cola es serial, así que dos llamados a &lt;code&gt;read()&lt;/code&gt; desde hilos distintos también se serializan, aunque leer no modifique el estado y en principio no haya conflicto entre ellas. Ese costo es el que un &lt;code&gt;DispatchQueue&lt;/code&gt; concurrente con &lt;code&gt;DispatchBarrier&lt;/code&gt; evita, a cambio de más complejidad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Falta ver qué cambia cuando, sobre la misma cola serial, se despacha trabajo con &lt;code&gt;async&lt;/code&gt; en lugar de &lt;code&gt;sync&lt;/code&gt;. Eso es lo que cubre el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)" rel="noopener noreferrer"&gt;sync(execute:) (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;DispatchQueue (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Dispatch Queue: cola FIFO, prioridad y tipos de colas</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 22:17:10 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-dispatch-queue-cola-fifo-prioridad-y-tipos-de-colas-963</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-dispatch-queue-cola-fifo-prioridad-y-tipos-de-colas-963</guid>
      <description>&lt;p&gt;Una &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchQueue&lt;/code&gt;&lt;/a&gt; es el objeto al que se le envía trabajo en GCD. No se le envían hilos, se le envían closures (bloques de código), y es la cola la que decide cómo y cuándo ejecutarlas.&lt;/p&gt;

&lt;h2&gt;
  
  
  FIFO: orden de entrega, no necesariamente orden real de inicio
&lt;/h2&gt;

&lt;p&gt;Una &lt;code&gt;DispatchQueue&lt;/code&gt; es una cola FIFO (&lt;em&gt;first in, first out&lt;/em&gt;): GCD &lt;strong&gt;entrega&lt;/strong&gt; los closures para ejecución en el mismo orden en que se despacharon. En una cola serial esa garantía coincide con el orden real de inicio, porque solo hay un carril de ejecución: un closure no puede empezar hasta que el anterior termine.&lt;/p&gt;

&lt;p&gt;En una cola concurrente esa coincidencia se rompe. GCD entrega los closures a hilos independientes en orden FIFO, pero una vez entregados, es el scheduler del sistema operativo (no GCD) quien decide cuándo cada hilo ejecuta su primera instrucción. Un closure despachado después puede imprimir su primera línea antes que uno despachado antes, sobre todo si requiere menos trabajo de arranque (por ejemplo, &lt;code&gt;sync&lt;/code&gt; puede reutilizar el hilo que hace el llamado en lugar de pedir uno nuevo del pool, mientras que cada &lt;code&gt;async&lt;/code&gt; sí necesita adquirirlo). El artículo de DQ Concurrente: ejecución síncrona muestra un caso real de esto.&lt;/p&gt;

&lt;p&gt;El orden de finalización tampoco está garantizado en una cola concurrente. Eso depende de si la cola es serial o concurrente, lo cual se cubre en los próximos dos artículos. Lo único que GCD garantiza siempre, en cualquier tipo de cola, es el orden de entrega FIFO descrito arriba.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prioridad: Quality of Service (QoS)
&lt;/h2&gt;

&lt;p&gt;Cada tarea que se despacha tiene una prioridad asociada, expresada como &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqos" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchQoS&lt;/code&gt;&lt;/a&gt;. El sistema usa esa prioridad para decidir qué tarea atender primero cuando varias compiten por CPU:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.userInteractive&lt;/code&gt;: la más alta. No significa "corre en &lt;code&gt;main&lt;/code&gt;" ni "bloquea la interfaz". El bloqueo de la UI solo ocurre si el trabajo corre en el hilo principal, y eso lo decide la cola, no el QoS. Es para trabajo cuya tardanza haría que la interfaz se sienta congelada (animaciones, o un cálculo en background cuyo resultado alimenta de inmediato a la UI).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.userInitiated&lt;/code&gt;: trabajo que el usuario está esperando activamente (abrir un documento, procesar el resultado de un tap).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.default&lt;/code&gt;: prioridad por defecto cuando no se especifica una explícita.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.utility&lt;/code&gt;: trabajo de larga duración que no necesita resultado inmediato (descargas, procesamiento de datos, con barra de progreso visible).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.background&lt;/code&gt;: la más baja. Trabajo que el usuario no percibe (sincronización en segundo plano, indexación).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El QoS es una señal relativa de prioridad, no una garantía de velocidad. El scheduler del sistema la usa para decidir, cuando hay más hilos con trabajo pendiente que núcleos disponibles, a cuáles asignarles tiempo de CPU primero. Si todas las tareas del proceso declaran &lt;code&gt;.userInteractive&lt;/code&gt;, el scheduler pierde la información que necesita para diferenciarlas: todas piden el mismo nivel de servicio, así que terminan repartiéndose los mismos núcleos por turnos (time-slicing), igual que pasaría si todas tuvieran &lt;code&gt;.default&lt;/code&gt;. El resultado medible es que ninguna tarea termina antes por estar marcada &lt;code&gt;.userInteractive&lt;/code&gt;: el código de animación que sí necesita esa prioridad ahora compite por CPU contra trabajo que no la necesitaba, con las mismas probabilidades de ser interrumpido.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tipos de cola
&lt;/h2&gt;

&lt;p&gt;GCD expone tres formas de obtener una &lt;code&gt;DispatchQueue&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// 1. Cola principal: serial, ligada al hilo principal (UI)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;main&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Colas globales: concurrentes, provistas por el sistema, una por cada QoS&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;global&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userInitiated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Cola propia: serial por defecto&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;custom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.tareas"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// 3b. Cola propia, hecha concurrente explícitamente&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;customConcurrent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dev.goyes.tareas-concurrentes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concurrent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cola principal (&lt;code&gt;DispatchQueue.main&lt;/code&gt;).&lt;/strong&gt; Es serial y siempre ejecuta en el hilo principal. Todo lo que toca UI debe pasar por aquí.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colas globales (&lt;code&gt;DispatchQueue.global(qos:)&lt;/code&gt;).&lt;/strong&gt; Son concurrentes y las administra el sistema. No se crean, se obtienen: hay una por cada nivel de QoS, y se comparten con el resto de la app (y potencialmente con otros procesos del sistema). La firma es &lt;code&gt;global(qos: DispatchQoS.QoSClass = .default)&lt;/code&gt;: el parámetro tiene un valor por defecto, así que &lt;code&gt;DispatchQueue.global()&lt;/code&gt; sin argumentos no significa "sin QoS". Equivale explícitamente a &lt;code&gt;DispatchQueue.global(qos: .default)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Colas propias (&lt;code&gt;DispatchQueue(label:)&lt;/code&gt;).&lt;/strong&gt; Las crea el desarrollador. Por defecto son seriales; se vuelven concurrentes solo si se especifica &lt;code&gt;attributes: .concurrent&lt;/code&gt;. El &lt;code&gt;label&lt;/code&gt; no afecta el comportamiento: sirve únicamente para identificar la cola en herramientas de depuración como Instruments.&lt;/p&gt;

&lt;p&gt;Nota: en el código de arriba, solo la cola global muestra explícitamente un QoS (&lt;code&gt;qos: .userInitiated&lt;/code&gt;). No es que &lt;code&gt;DispatchQueue.main&lt;/code&gt; y &lt;code&gt;DispatchQueue(label:)&lt;/code&gt; carezcan de QoS. &lt;code&gt;DispatchQueue.main&lt;/code&gt; corre siempre con la prioridad más alta del sistema y no se puede reconfigurar, y &lt;code&gt;DispatchQueue(label:)&lt;/code&gt; sí acepta un parámetro &lt;code&gt;qos:&lt;/code&gt; en su inicializador completo (&lt;code&gt;init(label:qos:attributes:autoreleaseFrequency:target:)&lt;/code&gt;), simplemente no es necesario todavía para lo que cubre este artículo. Cómo asignar QoS a una cola propia se explica en un artículo posterior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;Ya con cola serial vs. concurrente identificadas, lo que falta es ver cómo cambia el comportamiento al despachar trabajo de forma síncrona o asíncrona en cada una. Eso son los siguientes cuatro artículos.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;DispatchQueue (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqos" rel="noopener noreferrer"&gt;DispatchQoS (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
    <item>
      <title>[GCD] Gestionando hilos de forma segura y optimizada</title>
      <dc:creator>GoyesDev</dc:creator>
      <pubDate>Tue, 30 Jun 2026 22:04:40 +0000</pubDate>
      <link>https://dev.to/david_goyes_a488f58a17a53/gcd-gestionando-hilos-de-forma-segura-y-optimizada-4f28</link>
      <guid>https://dev.to/david_goyes_a488f58a17a53/gcd-gestionando-hilos-de-forma-segura-y-optimizada-4f28</guid>
      <description>&lt;p&gt;Antes de &lt;a href="https://developer.apple.com/documentation/DISPATCH" rel="noopener noreferrer"&gt;&lt;code&gt;Grand Central Dispatch&lt;/code&gt;&lt;/a&gt; (GCD), escribir código concurrente en Cocoa significaba administrar hilos a mano: crear un &lt;a href="https://developer.apple.com/documentation/foundation/thread" rel="noopener noreferrer"&gt;&lt;code&gt;NSThread&lt;/code&gt;&lt;/a&gt; o un &lt;code&gt;pthread&lt;/code&gt;, decidir cuántos crear, sincronizar el acceso a los datos compartidos con locks, y destruirlos cuando ya no se necesitaban. Cada una de esas decisiones era responsabilidad del desarrollador, y cada una era una oportunidad para introducir un bug: crear demasiados hilos, olvidar un lock, o dejar un hilo vivo más tiempo del necesario.&lt;/p&gt;

&lt;p&gt;Apple presentó GCD en el WWDC de 2009, junto con Mac OS X Snow Leopard. Su implementación, &lt;code&gt;libdispatch&lt;/code&gt;, se liberó como código abierto ese mismo año. La idea central fue cambiar la pregunta que se hace el desarrollador: en lugar de pensar "¿en qué hilo va a correr esto?", GCD propone pensar "¿en qué cola va a correr esto?".&lt;/p&gt;

&lt;h2&gt;
  
  
  El cambio de modelo: de hilos a colas
&lt;/h2&gt;

&lt;p&gt;En GCD no se crean hilos directamente. Se envía un bloque de trabajo (un closure) a una &lt;a href="https://developer.apple.com/documentation/dispatch/dispatchqueue" rel="noopener noreferrer"&gt;&lt;code&gt;DispatchQueue&lt;/code&gt;&lt;/a&gt;, y es el sistema operativo quien decide en qué hilo se ejecuta, cuándo, y cuántos hilos mantener vivos. El desarrollador delega esa responsabilidad.&lt;/p&gt;

&lt;p&gt;Consideremos el siguiente fragmento de código, donde se despacha trabajo tanto a la cola global concurrente como a la cola principal, y se comprueba en qué hilo corre cada una:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;global&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;qos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// true, corrió en un hilo de segundo plano&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;DispatchQueue&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// true, corrió en el hilo principal&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hay creación explícita de hilos en ninguno de los dos casos. Solo se describe el trabajo y se envía a una cola. Es la cola la que decide en qué hilo corre: una cola global concurrente usa un hilo de segundo plano; la cola principal siempre usa el hilo principal.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Por qué es "seguro y optimizado"?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Seguro&lt;/strong&gt;, porque la cola es la responsable de administrar el ciclo de vida de los hilos. Ya no se gestiona manualmente la creación ni la destrucción, lo que elimina una categoría entera de errores propios de la administración manual de hilos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimizado&lt;/strong&gt;, porque GCD ajusta el tamaño del pool de hilos según la cantidad de núcleos disponibles y la carga del sistema en tiempo de ejecución, en lugar de usar un número fijo de hilos decidido a priori por el desarrollador. Esto evita tanto la sub-utilización del CPU (pocos hilos, núcleos desaprovechados) como la sobre-asignación (más hilos que núcleos, con el costo de cambio de contexto que eso implica).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que viene
&lt;/h2&gt;

&lt;p&gt;GCD organiza el trabajo en &lt;code&gt;DispatchQueue&lt;/code&gt;, que es el concepto central de todo lo demás en este módulo: tipos de cola, prioridad, y las distintas formas de despachar tareas (síncrona vs. asíncrona, serial vs. concurrente). Eso es lo que cubre el siguiente artículo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bibliografía
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/DISPATCH" rel="noopener noreferrer"&gt;Dispatch (Apple Developer Documentation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html" rel="noopener noreferrer"&gt;Concurrency Programming Guide (Apple)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Grand_Central_Dispatch" rel="noopener noreferrer"&gt;Grand Central Dispatch (Wikipedia)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/apple/swift-corelibs-libdispatch" rel="noopener noreferrer"&gt;apple/swift-corelibs-libdispatch: código fuente de &lt;code&gt;libdispatch&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gcd</category>
      <category>ios</category>
      <category>swift</category>
      <category>multithreading</category>
    </item>
  </channel>
</rss>
