上两节我们建了一个并行运算组件库,实现了一些基本的并行运算功能。到现在这个阶段,编写并行运算函数已经可以和数学代数解题相近了:我们了解了问题需求,然后从类型匹配入手逐步产生题解。下面我们再多做几个练习吧。
在上节我们介绍了asyncF,它的类型款式是这样的:asyncF(f: A => B): A => Par[B],从类型款式(type signature)分析,asyncF函数的功能是把一个普通的函数 A => B转成A => Par[B],Par[B]是一个并行运算。也就是说asyncF可以把一个输入参数A的函数变成一个同样输入参数A的并行运算。asyncF函数可以把List[A],一串A值,按照函数A => B变成List[Par[A]],即一串并行运算。
例:函数f: (a: A) => a + 10:List(1,2,3).map(asyncF(f))=List(Par(1+10),Par(2+10),Par(3+10)),这些Par是并行运算的。但它们的运算结果需要另一个函数sequence来读取。我们从以上分析可以得出sequence的类型款式:
def sequence[A](lp: List[Par[A]]): Par[List[A]]
def parMap[A,B](l: List[A])(f: A => B): Par[List[B]]
1、lp: List[Par[B]] = l.map(asyncF(f))
2、pl: Par[List[B]] = sequence(lp) >>> parMap
再做个新的习题:用并行运算方式Filter List:
def parFilter[A](as: List[A])(f: A => Boolean): Par[List[A]]
1、asyncF( a => if(f(a)) List(a) else List() ) >>> Par[List[A]]
2、lpl: List[Par[List[A]]] = as.map( asyncF( a => if(f(a)) List(a) else List()))
3、pll: Par[List[List[A]]] = sequence(lpl)
4、map(pll){ a => a.flatten } >>> Par[List{A]]
def parFilter[A](as: List[A])(f: A => Boolean): Par[List[A]] = { val pars: List[Par[List[A]]] = as.map(asyncF( (a: A) => if (f(a)) List(a) else List() )) map(sequence(pars)){ a => a.flatten } } //> parFilter: [A](as: List[A])(f: A => Boolean)ch71.Par.Par[List[A]]
parFilter(List(10,29,13,3,6,48)){_ > 10}(es).get//> pool-1-thread-1 //| pool-1-thread-2 //| pool-1-thread-3 //| pool-1-thread-4 //| pool-1-thread-5 //| pool-1-thread-6 //| pool-1-thread-7 //| pool-1-thread-8 //| pool-1-thread-9 //| pool-1-thread-10 //| pool-1-thread-11 //| pool-1-thread-12 //| pool-1-thread-14 //| pool-1-thread-16 //| pool-1-thread-13 //| pool-1-thread-15 //| pool-1-thread-17 //| res0: List[Int] = List(29, 13, 48)
1、"the quick fox".split(' ').size >>> 把字符串分解成文字并计算数量
2、List(A,B,C) >>> A.size + B.size + C.size >>> 把List里的文字数积合。
这两步可以分两个函数来实现:
1. f: A => B >>> 我们需要把这个函数转成并行运算:List[Par[B]]
2. g: List[B] => B
def generalWordCount[A,B](as: List[A])(f: A => B)(g: List[B] => B): Par[B] = { val lp: List[Par[B]] = as.map(asyncF(f)) val pl: Par[List[B]] = sequence(lp) map(pl)(g) } //> generalWordCount: [A, B](as: List[A])(f: A => B)(g: List[B] => B)ch71.Par.P //| ar[B]
def wordCount(as: List[String]): Par[Int] = { generalWordCount(as)(_.split(' ').size)(_.sum) } //> wordCount: (as: List[String])ch71.Par.Par[Int]
val lw = List("the quick silver fox", "is running","the one legged fog", "is hopping") //> lw : List[String] = List(the quick silver fox, is running, the one legged //| fog, is hopping) wordCount(lw)(es).get //> pool-1-thread-1 //| pool-1-thread-3 //| pool-1-thread-2 //| pool-1-thread-15 //| pool-1-thread-16 //| pool-1-thread-7 //| pool-1-thread-10 //| pool-1-thread-14 //| pool-1-thread-6 //| pool-1-thread-13 //| pool-1-thread-9 //| res7: Int = 12
相信大家对泛函编程的这种数学解题模式已经有了一定的了解。
在前面我们曾经提过现在的fork实现方式如果使用固定数量线程池的话有可能造成锁死:
val es = Executors.newFixedThreadPool(1) val a = fork(async(40+2)) run(es)(a).get
我们再回顾一下fork的实现:
def fork[A](pa: => Par[A]): Par[A] = { es => { es.submit(new Callable[A] { def call: A = run(es)(pa).get }) } }
我们再看看现在所有的组件函数是否足够应付所有问题,还需不需要增加一些基本组件,这也是开发一个函数库必须走的过程;这就是一个不断更新的过程。
现在有个新问题:如果一个并行运算的运行依赖另一个并行运算的结果,应该怎样解决?先看看问题的类型款式:
def choice[A](pa: Par[Boolean])(ifTrue: Par[A], ifFalse: Par[A]): Par[A]
def choice[A](pa: Par[Boolean])(ifTrue: Par[A], ifFalse: Par[A]): Par[A] = { es => if(run(es)(pa).get) run(es)(ifTrue) else run(es)(ifFalse) }
ppa: Par[Par[A]], 如果 run(es)(ppa).get 得到 pa: Par[A], 再run(es)(pa) >>> Future[A]。 Par[A] = es => Future[A],不就解决问题了嘛:
def join[A](ppa: Par[Par[A]]): Par[A] = { es => { run(es)(run(es)(ppa).get()) } }
既然我们能在两个并行运算中选择,那么能在N个并行运算中选择不是能更抽象吗?
def choiceN[A](pb: Par[Int])(choices: List[Par[A]]): Par[A]
def choiceN[A](pb: Par[Int])(choices: List[Par[A]]): Par[A] = { es => { run(es)(choices(run(es)(pb).get)) } }
def flatMap[A,B](pa: Par[A])(f: A => Par[B]): Par[B]
def flatMap[A,B](pa: Par[A])(f: A => Par[B]): Par[B] = { es => { run(es)(f(run(es)(pa).get)) } }
def choiceByFlatMap[A](pb: Par[Boolean])(ifTrue: Par[A], ifFalse: Par[A]): Par[A] ={ flatMap(pb){a => if (a) ifTrue else ifFalse } } def choiceNByFlatMap[A](pb: Par[Int])(choices: List[Par[A]]): Par[A] = { flatMap(pb){choices(_)} }
那么flatMap,join,map之间有没有什么数学关系呢?
def joinByFlatMap[A](ppa: Par[Par[A]]): Par[A] = { flatMap(ppa){(x: Par[A]) => x} } def flatMapByJoin[A,B](pa: Par[A])(f: A => Par[B]): Par[B] = { join(map(pa)(f)) } def mapByFlatMap[A,B](pa: Par[A])(f: A => B): Par[B] = { flatMap(pa) { a => unit(f(a)) } }