14.3.3_实现辅助函数

14.3.3 实现辅助函数

 

    在讨论计算动物位置的代码之前,我们要稍稍离题。我们需要实现几个函数,将由确定动物和捕食者位置的算法使用。出于各种用途,这些函数需要使用随机数,要正确地生成随机数,首先需要讨论如何安全地访问,那些不是线程安全(thread-safe)的对象。这可能是个问题,当我们处理具有可变状态的对象时,这是 .NET 类型的通常情况。

 

安全地访问共享对象

 

    Random 类常用的 .NET 类,它不是线程安全的。在我们的应用程序中,需要生成随机位置,以选择动物或捕食者应将移动到的位置。这个函数可以同时从多个线程中调用。然而,Random 需要初始化一次,然后一遍又一遍地使用。(如果你为每个调用创建 Random 新的实例,通常会得到重复的数字,给随机数生成器作为初始"种子"是取自系统时间。)如果你在同一实例上,从多个线程中调用 Next 方法,方法的行为是未定义的。实际上,它最终开始返回零。我们负责保证,每次将只有一个线程访问一个对象。

    要避免这个问题,我们可以使用锁,它会阻止正在执行代码的其他线程,直到操作完成,由相同的锁保护。这使代码高效降低。清单 14.24 提供了一个解决方案,它是安全的,但效率更高。

 

Listing 14.24 Safe way for generating random numbers (F# and C#)

 

F#

C#

module SafeRandom =
  let private rnd =
    new Random()

  let New() =
    lock rnd (fun () �C>
      new Random(rnd.Next())
    )

static class SafeRandom {
  static Random rnd =
    new Random();
  public static Random New() {
    lock (rnd)
      return new
        Random(rnd.Next());
  }
}

 

    我们在 F# 中创建一个模块,在 C# 中创建一个静态类,两者的目的相同:可以用来生成随机数生成器。创建两个生成器,使用的随机种子,来自一个全局随机数生成器,在锁内可以安全地访问。由于这种做法,我们不必每次在需要创建一个随机数字时,使用锁。我们只需要每次创建一个新的生成器,在执行可以与其他任务并行执行的操作时。在一个线程内,可以安全地重用同一实例,知道没有其他任何线程会对它访问。

    F# 中与 C# 的 lock 关键字等效的是 lock 函数,它取一个简单的函数作为参数。它要求这个锁,使用 Monitor 类,运行指定的函数,然后释放锁。在下一节中,我们会好好利用 SafeRandom,在仿真世界中,需要在某些地点生成随机位置。

 

处理位置

 

    当生成动物的位置,我们需要生成随机的位置,作为可能的目标;然后,我们需要某种方式,来测量哪个位置是最佳的选择。这可能取决于和其他捕食者及动物有多近。要写这些算法,我们需要几个函数。我们来看看它们的类型签名,它应该给你足够的信息,来了解如何它们中如何工作的,以及我们自己如何实现。下面的代码片断显示其注释的 F# 类型签名:

 

/// Returns the distance between two specified locations
val distance : Vector -> Vector �C> float

/// Returns specified number of check-points
/// on the path between the specified locations
val getPathPoints : int * Vector * Vector -&gt; seq<Vector>

/// Returns the specified number of randomly generated locations
val randomLocations : int -&gt; seq<Vector>

 

    第一个函数可以使用 Math.Pow 和 Math.Sqrt 来实现。注意,我们给这个函数几个参数,可以使用偏应用。在 F# 中,当我们要计算从一个特定的点,到集合中地点的距离时,是方便的。第二和第三个函数可以使用 F# 中的序列表达式和 C# 中的迭代器实现。

    我们需要从并行运行的多个线程中调用 randomLocations 函数,所以,要用到先前创建的 SafeRandom 模块。每次调用 randomLocations,首先使用 SafeRandom.New (),创建一个新的随机数字生成器,然后,重复使用这个生成器去生成结果。结果是一个延迟序列,因此,它实际是在需要时才生成。正巧,我们需要这个序列中的所有项目,来计算位置的动物,所以,这并不产生大的差异。

    这三个辅助函数都是很简单的,但我们当实现仿真时,一直都会用到它们。由于有了这三个函数和丰富的标准 F# 库,运动的算法可以用简捷的方式来写。

你可能感兴趣的:(职场,安全,如何,休闲,动物)