<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>0x9bb1</title>
        <link>https://paragraph.com/@zhangxin2</link>
        <description>undefined</description>
        <lastBuildDate>Tue, 14 Apr 2026 22:22:44 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>0x9bb1</title>
            <url>https://storage.googleapis.com/papyrus_images/18ba1d655755d2ac9f534b52833b16394dd06a5695f5abfbc4496b4fd3fb2c3d.jpg</url>
            <link>https://paragraph.com/@zhangxin2</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[并发模型]]></title>
            <link>https://paragraph.com/@zhangxin2/w1fAaa9rPxz3mVoQXlJo</link>
            <guid>w1fAaa9rPxz3mVoQXlJo</guid>
            <pubDate>Mon, 14 Mar 2022 09:13:04 GMT</pubDate>
            <description><![CDATA[并发模型指定了系统中的线程如何通过协作来完成分配给它们的作业。不同的并发模型采用不同的方式拆分作业，同时线程间的协作和交互方式也不相同。 无论哪一种模型,其任务调度方法都既可以是协作式的(cooperative),也可以是抢占式的(preemptive),协作式任务调度的时机由任务自身确定,当且仅当当前任务 主动放弃执行权,调度器才会将执行权交给其他任务;而抢占式任务调度的时机由底层软件(通常是操作系统) 确定,一个任务可能在任何时候被唤醒或挂起,因此,不同任务也可能并行执行。 协作式事件驱动模型和抢占式多线程模型是最常用的两种并发任务 处理技术。它们是对偶模型。 并行工作者模型在并行工作者模型中，委派者（Delegator）将传入的作业分配给不同的工作者。每个工作者完成整个任务。工作者们并行运作在不同的线程上，甚至可能在不同的CPU上。在Java应用系统中，并行工作者模型是最常见的并发模型（即使正在转变）。java.util.concurrent包中的许多并发实用工具都是设计用于这个模型的。并行工作者模式的优点是，它很容易理解。你只需添加更多的工作者来提高系统的并行度。共享的工...]]></description>
            <content:encoded><![CDATA[<p><strong>并发模型指定了系统中的线程如何通过协作来完成分配给它们的作业。不同的并发模型采用不同的方式拆分作业，同时线程间的协作和交互方式也不相同。</strong></p><p><strong>无论哪一种模型,其任务调度方法都既可以是协作式的(cooperative),也可以是抢占式的(preemptive),协作式任务调度的时机由任务自身确定,当且仅当当前任务 主动放弃执行权,调度器才会将执行权交给其他任务;而抢占式任务调度的时机由底层软件(通常是操作系统) 确定,一个任务可能在任何时候被唤醒或挂起,因此,不同任务也可能并行执行。</strong></p><p><strong>协作式事件驱动模型和抢占式多线程模型是最常用的两种并发任务 处理技术。它们是对偶模型。</strong></p><p><strong><em>并行工作者模型</em></strong></p><ul><li><p>在并行工作者模型中，委派者（Delegator）将传入的作业分配给不同的工作者。每个工作者完成整个任务。工作者们并行运作在不同的线程上，甚至可能在不同的CPU上。在Java应用系统中，并行工作者模型是最常见的并发模型（即使正在转变）。<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://tutorials.jenkov.com/java-util-concurrent/index.html">java.util.concurrent包</a>中的许多并发实用工具都是设计用于这个模型的。</p></li><li><p>并行工作者模式的优点是，它很容易理解。你只需添加更多的工作者来提高系统的并行度。</p></li><li><p>共享的工作者经常需要访问一些共享数据，一旦共享状态潜入到并行工作者模型中，将会使情况变得复杂起来。</p><ul><li><p>线程需要以某种方式存取共享数据，以确保某个线程的修改能够对其他线程可见（数据修改需要同步到主存中，不仅仅将数据保存在执行这个线程的CPU的缓存中）。线程需要避免<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://tutorials.jenkov.com/java-concurrency/race-conditions-and-critical-sections.html">竟态</a>，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://tutorials.jenkov.com/java-concurrency/deadlock.html">死锁</a>以及很多其他共享状态的并发性问题。</p><ul><li><p>可持久化的数据结构是另一种选择。在修改的时候，可持久化的数据结构总是保护它的前一个版本不受影响。因此，如果多个线程指向同一个可持久化的数据结构，并且其中一个线程进行了修改，进行修改的线程会获得一个指向新结构的引用。所有其他线程保持对旧结构的引用，旧结构没有被修改并且因此保证一致性。Scala编程包含几个持久化数据结构。【注：这里的可持久化数据结构不是指持久化存储，而是一种数据结构，比如Java中的String类，以及CopyOnWriteArrayList类，具体可<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://www.cnblogs.com/tedzhao/archive/2008/11/12/1332112.html">参考</a>】</p></li><li><p>并行工作者模式的另一个缺点是，作业执行顺序是不确定的。无法保证哪个作业最先或者最后被执行。作业A可能在作业B之前就被分配工作者了，但是作业B反而有可能在作业A之前执行。并行工作者模式的这种非确定性的特性，使得很难在任何特定的时间点推断系统的状态。这也使得它也更难（如果不是不可能的话）保证一个作业在其他作业之前被执行。</p></li></ul></li></ul></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/38ca560a628d37478751f80ae0ab879ef559a0c18f0d96878fe8049dca3d2997.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><ul><li><p><strong><em>流水线并发模型/事件驱动系统</em></strong></p><ul><li><p>类似于工厂中生产线上的工人们那样组织工作者。每个工作者只负责作业中的部分工作。当完成了自己的这部分工作时工作者会将作业转发给下一个工作者。每个工作者在自己的线程中运行，并且不会和其他工作者共享状态。有时也被成为<em>无共享</em>并行模型。</p></li><li><p>工作者之间无需共享状态，意味着实现的时候无需考虑所有因并发访问共享对象而产生的并发性问题。这使得在实现工作者的时候变得非常容易。在实现工作者的时候就好像是单个线程在处理工作-基本上是一个单线程的实现。</p></li><li><p>当工作者知道了没有其他线程可以修改它们的数据，工作者可以变成有状态的。对于有状态，我是指，它们可以在内存中保存它们需要操作的数据，只需在最后将更改写回到外部存储系统。因此，有状态的工作者通常比无状态的工作者具有更高的性能。</p></li><li><p>流水线并发模型最大的缺点是作业的执行往往分布到多个工作者上，并因此分布到项目中的多个类上。这样导致在追踪某个作业到底被什么代码执行时变得困难。</p><p>同样，这也加大了代码编写的难度。有时会将工作者的代码写成回调处理的形式。若在代码中嵌入过多的回调处理，往往会出现所谓的回调地狱（callback hell）现象。所谓回调地狱，就是意味着在追踪代码在回调过程中到底做了什么，以及确保每个回调只访问它需要的数据的时候，变得非常困难。</p></li><li><p>事件驱动模型的主要问题可以概括为这样几类:控制流反转、函数分裂、变量重定位、调用栈重构和侵入性原则。</p><ul><li><p>与控制流翻转相伴的往往是函数分裂</p></li><li><p>函数分裂会迫使原本在栈中的函数局部变量转移到堆中,造成变量重定位</p></li><li><p>在调试事件驱动程序的时候,开发人员往往需要手工重新构造函数调用堆栈,造成调用栈重构问题</p></li><li><p>函数分裂具有侵入性原则,因为一个分裂函数会强制分裂它的直接和间接调用者</p></li><li><p>事件驱动模型原本应属于实现细节,然而其侵入性却导致这个细节必须暴露在模块接口之中,造成了模块 的低封装性</p></li></ul></li><li><p>Actors 和 channels 是两种比较类似的流水线（或反应器/事件驱动）模型。</p><ul><li><p>actors</p><ul><li><p>在Actor模型中每个工作者被称为actor。Actor之间可以直接异步地发送和处理消息。Actor可以被用来实现一个或多个像前文描述的那样的作业处理流水线。</p></li><li><p>如akka</p><pre data-type="codeBlock" text="object Greeter {
  final case class Greet(whom: String, replyTo: ActorRef[Greeted])
  final case class Greeted(whom: String, from: ActorRef[Greet])

  def apply(): Behavior[Greet] = Behaviors.receive { (context, message) =&gt;
    context.log.info(&quot;Hello {}!&quot;, message.whom)
    message.replyTo ! Greeted(message.whom, context.self)
    Behaviors.same
  }
}

object GreeterBot {

  def apply(max: Int): Behavior[Greeter.Greeted] = {
    bot(0, max)
  }

  private def bot(greetingCounter: Int, max: Int): Behavior[Greeter.Greeted] =
    Behaviors.receive { (context, message) =&gt;
      val n = greetingCounter + 1
      context.log.info(&quot;Greeting {} for {}&quot;, n, message.whom)
      if (n == max) {
        Behaviors.stopped
      } else {
        message.from ! Greeter.Greet(message.whom, context.self)
        bot(n, max)
      }
    }
}



object GreeterMain {

  final case class SayHello(name: String)

  def apply(): Behavior[SayHello] =
    Behaviors.setup { context =&gt;
      val greeter = context.spawn(Greeter(), &quot;greeter&quot;)

      Behaviors.receiveMessage { message =&gt;
        val replyTo = context.spawn(GreeterBot(max = 3), message.name)
        greeter ! Greeter.Greet(message.name, replyTo)
        Behaviors.same
      }
    }
}
"><code>object Greeter {
  final case class Greet(whom: String, replyTo: ActorRef[Greeted])
  final case class Greeted(whom: String, <span class="hljs-keyword">from</span>: ActorRef[Greet])

  def apply(): Behavior[Greet] <span class="hljs-operator">=</span> Behaviors.receive { (context, message) <span class="hljs-operator">=</span><span class="hljs-operator">></span>
    context.log.info(<span class="hljs-string">"Hello {}!"</span>, message.whom)
    message.replyTo <span class="hljs-operator">!</span> Greeted(message.whom, context.self)
    Behaviors.same
  }
}

object GreeterBot {

  def apply(max: Int): Behavior[Greeter.Greeted] <span class="hljs-operator">=</span> {
    bot(<span class="hljs-number">0</span>, max)
  }

  <span class="hljs-keyword">private</span> def bot(greetingCounter: Int, max: Int): Behavior[Greeter.Greeted] <span class="hljs-operator">=</span>
    Behaviors.receive { (context, message) <span class="hljs-operator">=</span><span class="hljs-operator">></span>
      val n <span class="hljs-operator">=</span> greetingCounter <span class="hljs-operator">+</span> <span class="hljs-number">1</span>
      context.log.info(<span class="hljs-string">"Greeting {} for {}"</span>, n, message.whom)
      <span class="hljs-keyword">if</span> (n <span class="hljs-operator">=</span><span class="hljs-operator">=</span> max) {
        Behaviors.stopped
      } <span class="hljs-keyword">else</span> {
        message.from <span class="hljs-operator">!</span> Greeter.Greet(message.whom, context.self)
        bot(n, max)
      }
    }
}



object GreeterMain {

  final case class SayHello(name: String)

  def apply(): Behavior[SayHello] <span class="hljs-operator">=</span>
    Behaviors.setup { context <span class="hljs-operator">=</span><span class="hljs-operator">></span>
      val greeter <span class="hljs-operator">=</span> context.spawn(Greeter(), <span class="hljs-string">"greeter"</span>)

      Behaviors.receiveMessage { message <span class="hljs-operator">=</span><span class="hljs-operator">></span>
        val replyTo <span class="hljs-operator">=</span> context.spawn(GreeterBot(max <span class="hljs-operator">=</span> <span class="hljs-number">3</span>), message.<span class="hljs-built_in">name</span>)
        greeter <span class="hljs-operator">!</span> Greeter.Greet(message.<span class="hljs-built_in">name</span>, replyTo)
        Behaviors.same
      }
    }
}
</code></pre></li></ul></li><li><p>Channel</p><ul><li><p>而在Channel模型中，工作者之间不直接进行通信。相反，它们在不同的通道中发布自己的消息（事件）。其他工作者们可以在这些通道上监听消息，发送者无需知道谁在监听。</p></li><li><p>比如golang</p><pre data-type="codeBlock" text="package main

import &quot;fmt&quot;

func sum(s []int, c chan int) {
    sum := 0
    for _, v := range s {
        sum += v
    }
    c &lt;- sum // send sum to c
}

func main() {
    s := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(s[:len(s)/2], c)
    go sum(s[len(s)/2:], c)
    x, y := &lt;-c, &lt;-c // receive from c

    fmt.Println(x, y, x+y)
}
"><code><span class="hljs-keyword">package</span> main

<span class="hljs-keyword">import</span> <span class="hljs-string">"fmt"</span>

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">sum</span><span class="hljs-params">(s []<span class="hljs-type">int</span>, c <span class="hljs-keyword">chan</span> <span class="hljs-type">int</span>)</span></span> {
    sum := <span class="hljs-number">0</span>
    <span class="hljs-keyword">for</span> _, v := <span class="hljs-keyword">range</span> s {
        sum += v
    }
    c &#x3C;- sum <span class="hljs-comment">// send sum to c</span>
}

<span class="hljs-function"><span class="hljs-keyword">func</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span> {
    s := []<span class="hljs-type">int</span>{<span class="hljs-number">7</span>, <span class="hljs-number">2</span>, <span class="hljs-number">8</span>, <span class="hljs-number">-9</span>, <span class="hljs-number">4</span>, <span class="hljs-number">0</span>}

    c := <span class="hljs-built_in">make</span>(<span class="hljs-keyword">chan</span> <span class="hljs-type">int</span>)
    <span class="hljs-keyword">go</span> sum(s[:<span class="hljs-built_in">len</span>(s)/<span class="hljs-number">2</span>], c)
    <span class="hljs-keyword">go</span> sum(s[<span class="hljs-built_in">len</span>(s)/<span class="hljs-number">2</span>:], c)
    x, y := &#x3C;-c, &#x3C;-c <span class="hljs-comment">// receive from c</span>

    fmt.Println(x, y, x+y)
}
</code></pre></li></ul></li></ul></li></ul></li></ul>]]></content:encoded>
            <author>zhangxin2@newsletter.paragraph.com (0x9bb1)</author>
        </item>
    </channel>
</rss>