PageRenderTime 40ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/groovy/groovyx/gpars/Asynchronizer.groovy

http://gparallelizer.googlecode.com/
Groovy | 335 lines | 113 code | 23 blank | 199 comment | 3 complexity | e2517cfc3ba1c6afe27edc0b95d07213 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, Apache-2.0
  1. // GPars (formerly GParallelizer)
  2. //
  3. // Copyright Š 2008-9 The original author or authors
  4. //
  5. // Licensed under the Apache License, Version 2.0 (the "License");
  6. // you may not use this file except in compliance with the License.
  7. // You may obtain a copy of the License at
  8. //
  9. // http://www.apache.org/licenses/LICENSE-2.0
  10. //
  11. // Unless required by applicable law or agreed to in writing, software
  12. // distributed under the License is distributed on an "AS IS" BASIS,
  13. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. // See the License for the specific language governing permissions and
  15. // limitations under the License.
  16. package groovyx.gpars
  17. import groovyx.gpars.util.PoolUtils
  18. import java.lang.Thread.UncaughtExceptionHandler
  19. import java.util.concurrent.Callable
  20. import java.util.concurrent.ExecutorService
  21. import java.util.concurrent.Executors
  22. import java.util.concurrent.Future
  23. import java.util.concurrent.ThreadFactory
  24. import java.util.concurrent.TimeUnit
  25. import org.codehaus.groovy.runtime.InvokerInvocationException
  26. /**
  27. * Enables a ExecutorService-based DSL on closures, objects and collections.
  28. * E.g.
  29. * Asynchronizer.withAsynchronizer(5) {ExecutorService service ->
  30. * Collection<Future> result = [1, 2, 3, 4, 5].collectParallel({it * 10}.async())
  31. * assertEquals(new HashSet([10, 20, 30, 40, 50]), new HashSet((Collection)result*.get()))
  32. *}*
  33. * Asynchronizer.withAsynchronizer(5) {ExecutorService service ->
  34. * def result = [1, 2, 3, 4, 5].findParallel{Number number -> number > 2}* assert result in [3, 4, 5]
  35. *}*
  36. * @author Vaclav Pech
  37. * Date: Oct 23, 2008
  38. */
  39. class Asynchronizer {
  40. /**
  41. * Maps threads to their appropriate thread pools
  42. */
  43. private static final ThreadLocalPools currentPoolStack = new ThreadLocalPools()
  44. /**
  45. * Caches the default pool size.
  46. */
  47. private static final int defaultPoolSize = PoolUtils.retrieveDefaultPoolSize()
  48. /**
  49. * Retrieves the pool assigned to the current thread.
  50. */
  51. protected static ExecutorService retrieveCurrentPool() {
  52. currentPoolStack.current
  53. }
  54. /**
  55. * Creates a new pool with the default size()
  56. */
  57. private static createPool() {
  58. return createPool(PoolUtils.retrieveDefaultPoolSize())
  59. }
  60. /**
  61. * Creates a new pool with the given size()
  62. */
  63. private static createPool(int poolSize) {
  64. return createPool(poolSize, createDefaultThreadFactory())
  65. }
  66. private static createPool(int poolSize, ThreadFactory threadFactory) {
  67. if (!(poolSize in 1..Integer.MAX_VALUE)) throw new IllegalArgumentException("Invalid value $poolSize for the pool size has been specified. Please supply a positive int number.")
  68. if (!threadFactory) throw new IllegalArgumentException("No value specified for threadFactory.")
  69. return Executors.newFixedThreadPool(poolSize, threadFactory)
  70. }
  71. private static ThreadFactory createDefaultThreadFactory() {
  72. return {Runnable runnable ->
  73. final Thread thread = new Thread(runnable)
  74. thread.daemon = false
  75. thread
  76. } as ThreadFactory
  77. }
  78. /**
  79. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  80. * and runs the supplied closure.
  81. * It is an identical alternative for withAsynchronizer() with a shorter name.
  82. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  83. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  84. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  85. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  86. * for processing in the pool.
  87. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  88. * operation on each image in the <i>images</i> collection in parallel.
  89. * <pre>
  90. * def result = new ConcurrentSkipListSet()
  91. * Asynchronizer.doParallel {ExecutorService service ->
  92. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  93. *}* </pre>
  94. * @param cl The block of code to invoke with the DSL enabled
  95. */
  96. public static doParallel(Closure cl) {
  97. return doParallel(defaultPoolSize, cl)
  98. }
  99. /**
  100. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  101. * and runs the supplied closure.
  102. * It is an identical alternative for withAsynchronizer() with a shorter name.
  103. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  104. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  105. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  106. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  107. * for processing in the pool.
  108. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  109. * operation on each image in the <i>images</i> collection in parallel.
  110. * <pre>
  111. * def result = new ConcurrentSkipListSet()
  112. * Asynchronizer.doParallel(5) {ExecutorService service ->
  113. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  114. *}* </pre>
  115. * @param numberOfThreads Number of threads in the newly created thread pool
  116. * @param cl The block of code to invoke with the DSL enabled
  117. */
  118. public static doParallel(int numberOfThreads, Closure cl) {
  119. return doParallel(numberOfThreads, createDefaultThreadFactory(), cl)
  120. }
  121. /**
  122. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  123. * and runs the supplied closure.
  124. * It is an identical alternative for withAsynchronizer() with a shorter name.
  125. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  126. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  127. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  128. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  129. * for processing in the pool.
  130. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  131. * operation on each image in the <i>images</i> collection in parallel.
  132. * <pre>
  133. * def result = new ConcurrentSkipListSet()
  134. * Asynchronizer.doParallel(5) {ExecutorService service ->
  135. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  136. *}* </pre>
  137. * @param numberOfThreads Number of threads in the newly created thread pool
  138. * @param threadFactory Factory for threads in the pool
  139. * @param cl The block of code to invoke with the DSL enabled
  140. */
  141. public static doParallel(int numberOfThreads, ThreadFactory threadFactory, Closure cl) {
  142. final ExecutorService pool = createPool(numberOfThreads, threadFactory)
  143. try {
  144. return withExistingAsynchronizer(pool, cl)
  145. } finally {
  146. pool.shutdown()
  147. pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)
  148. }
  149. }
  150. /**
  151. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  152. * and runs the supplied closure.
  153. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  154. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  155. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  156. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  157. * for processing in the pool.
  158. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  159. * operation on each image in the <i>images</i> collection in parallel.
  160. * <pre>
  161. * def result = new ConcurrentSkipListSet()
  162. * Asynchronizer.withAsynchronizer {ExecutorService service ->
  163. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  164. *}* </pre>
  165. * @param cl The block of code to invoke with the DSL enabled
  166. * @deprecated Use doParallel() instead
  167. */
  168. public static withAsynchronizer(Closure cl) {
  169. return withAsynchronizer(3, cl)
  170. }
  171. /**
  172. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  173. * and runs the supplied closure.
  174. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  175. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  176. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  177. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  178. * for processing in the pool.
  179. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  180. * operation on each image in the <i>images</i> collection in parallel.
  181. * <pre>
  182. * def result = new ConcurrentSkipListSet()
  183. * Asynchronizer.withAsynchronizer(5) {ExecutorService service ->
  184. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  185. *}* </pre>
  186. * @param numberOfThreads Number of threads in the newly created thread pool
  187. * @param cl The block of code to invoke with the DSL enabled
  188. * @deprecated Use doParallel() instead
  189. */
  190. public static withAsynchronizer(int numberOfThreads, Closure cl) {
  191. return withAsynchronizer(numberOfThreads, createDefaultThreadFactory(), cl)
  192. }
  193. /**
  194. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  195. * and runs the supplied closure.
  196. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  197. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  198. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  199. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  200. * for processing in the pool.
  201. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  202. * operation on each image in the <i>images</i> collection in parallel.
  203. * <pre>
  204. * def result = new ConcurrentSkipListSet()
  205. * Asynchronizer.withAsynchronizer(5) {ExecutorService service ->
  206. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  207. *}* </pre>
  208. * @param numberOfThreads Number of threads in the newly created thread pool
  209. * @param threadFactory Factory for threads in the pool
  210. * @param cl The block of code to invoke with the DSL enabled
  211. * @deprecated Use doParallel() instead
  212. */
  213. public static withAsynchronizer(int numberOfThreads, ThreadFactory threadFactory, Closure cl) {
  214. final ExecutorService pool = createPool(numberOfThreads, threadFactory)
  215. try {
  216. return withExistingAsynchronizer(pool, cl)
  217. } finally {
  218. pool.shutdown()
  219. pool.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS)
  220. }
  221. }
  222. /**
  223. * Creates a new instance of <i>ExecutorService</i>, binds it to the current thread, enables the ExecutorService DSL
  224. * and runs the supplied closure.
  225. * Within the supplied code block the <i>ExecutorService</i> is available as the only parameter, objects have been
  226. * enhanced with the <i>eachParallel()</i>, <i>collectParallel()</i> and other methods from the <i>AsyncInvokerUtil</i>
  227. * category class as well as closures can be turned into asynchronous ones by calling the <i>async()</i> method on them.
  228. * E.g. <i>closure,async</i> returns a new closure, which, when run will schedule the original closure
  229. * for processing in the pool.
  230. * Calling <i>images.eachParallel{processImage(it}}</i> will call the potentially long-lasting <i>processImage()</i>
  231. * operation on each image in the <i>images</i> collection in parallel.
  232. * <pre>
  233. * def result = new ConcurrentSkipListSet()
  234. * Asynchronizer.withAsynchronizer(5) {ExecutorService service ->
  235. * [1, 2, 3, 4, 5].eachParallel{Number number -> result.add(number * 10)}* assertEquals(new HashSet([10, 20, 30, 40, 50]), result)
  236. *}* </pre>
  237. * @param pool The <i>ExecutorService</i> to use, the service will not be shutdown after this method returns
  238. */
  239. public static withExistingAsynchronizer(ExecutorService pool, Closure cl) {
  240. currentPoolStack << pool
  241. def result = null
  242. try {
  243. use(AsyncInvokerUtil) {
  244. result = cl(pool)
  245. }
  246. } finally {
  247. currentPoolStack.pop()
  248. }
  249. return result
  250. }
  251. /**
  252. * Starts multiple closures in separate threads, collecting their return values
  253. * If an exception is thrown from the closure when called on any of the collection's elements,
  254. * it will be re-thrown in the calling thread when it calls the Future.get() method.
  255. * @return The result values of all closures
  256. * @throws AsyncException If any of the collection's elements causes the closure to throw an exception. The original exceptions will be stored in the AsyncException's concurrentExceptions field.
  257. */
  258. public static List<Object> doInParallel(Closure ... closures) {
  259. return AsyncInvokerUtil.processResult(executeAsync(closures))
  260. }
  261. /**
  262. * Starts multiple closures in separate threads, collecting Futures for their return values
  263. * If an exception is thrown from the closure when called on any of the collection's elements,
  264. * it will be re-thrown in the calling thread when it calls the Future.get() method.
  265. * @return Futures for the result values or exceptions of all closures
  266. */
  267. public static List<Future<Object>> executeAsync(Closure ... closures) {
  268. Asynchronizer.withAsynchronizer(closures.size()) {ExecutorService executorService ->
  269. List<Future<Object>> result = closures.collect {cl ->
  270. executorService.submit({
  271. cl.call()
  272. } as Callable<Object>)
  273. }
  274. result
  275. }
  276. }
  277. /**
  278. * Starts multiple closures in separate threads, using a new thread for the startup.
  279. * If any of the collection's elements causes the closure to throw an exception, an AsyncException is reported using System.err.
  280. * The original exceptions will be stored in the AsyncException's concurrentExceptions field.
  281. */
  282. public static void startInParallel(Closure ... closures) {
  283. startInParallel(createDefaultUncaughtExceptionHandler(), closures)
  284. }
  285. /**
  286. * Starts multiple closures in separate threads, using a new thread for the startup.
  287. * If any of the collection's elements causes the closure to throw an exception, an AsyncException is reported to the supplied instance of UncaughtExceptionHandler.
  288. * The original exceptions will be stored in the AsyncException's concurrentExceptions field.
  289. * Unwraps potential InvokerInvocationException before control is passed to the UncaughtExceptionHandler instance.
  290. * @return The thread that submits the closures to the thread executor service so that the caller can take ownership of it and e.g. call <i>join()</i> on it to wait for all the closures to finish processing.
  291. */
  292. public static Thread startInParallel(java.lang.Thread.UncaughtExceptionHandler uncaughtExceptionHandler, Closure ... closures) {
  293. final Thread thread = new Thread({
  294. doInParallel(closures)
  295. } as Runnable)
  296. thread.daemon = false
  297. thread.uncaughtExceptionHandler = {Thread t, Throwable throwable ->
  298. if (throwable instanceof InvokerInvocationException)
  299. uncaughtExceptionHandler.uncaughtException(t, throwable.cause)
  300. else
  301. uncaughtExceptionHandler.uncaughtException(t, throwable)
  302. } as UncaughtExceptionHandler
  303. thread.start()
  304. return thread
  305. }
  306. private static UncaughtExceptionHandler createDefaultUncaughtExceptionHandler() {
  307. return {Thread failedThread, Throwable throwable ->
  308. System.err.println "Error processing background thread ${failedThread.name}: ${throwable.message}"
  309. throwable.printStackTrace(System.err)
  310. } as UncaughtExceptionHandler
  311. }
  312. }