If you wrote multithreaded code in Groovy/Grails, I’m sure you stumbled upon the GPars library. It simplifies parallel processing – sometimes it’s as easy as wrapping a code fragment with a GPars closure, changing the iteration method (in our example from each to eachParallel) and passing the closure to a GPars pool. The library implementation handles the executor pool creation and adds new methods to collections.
Whenever I deliberately add multithreading, I always separate responsibilities (processing logic and concurrency handling). I wrap the single-threaded class with a new one, handing the parallel execution (creating a pool, managing threads, handling cancellation, etc.):
class MultithreadedProcessorService {
ProcessorService processorService
def threadPoolSize = ConfigurationHolder.config.threadPool.size
void processMultithreaded(batches) {
GParsPool.withPool(threadPoolSize) {
batches.eachParallel { batch ->
processorService.process(batch)
}
}
}
}
class ProcessorService {
void process(batch) {
// some processing
}
}
To test the multithreaded class you can you use Spock interactions. As opposed to Groovy and Grails mocks, they are thread-safe and you won’t get any weird and unstable results:
class MultithreadedProcessorServiceSpec extends UnitSpec {
private MultithreadedProcessorService multithreadedProcessorService
// Using Spock mocks because they are thread-safe
private ProcessorService processorService = Mock(ProcessorService)
def setup() {
mockConfig('threadPool.size=2')
multithreadedProcessorService = new MultithreadedProcessorService(
processorService: processorService
)
}
def 'processes batches using multiple threads'() {
given:
def batches = [[0, 1], [2, 3, 4], [5]]
def processedBatches = markedAsNotProcessed(batches)
when:
multithreadedProcessorService.processMultithreaded(batches)
then:
batches.size() * processorService.process({ batch ->
processedBatches[batch] = true
batch in batches
})
assertAllProcessed(processedBatches)
}
private markedAsNotProcessed(batches) {
batches.inject([:]) { processed, batch ->
processed[batch] = false
processed
}
}
private void assertAllProcessed(batches) {
assert batches*.value == [true] * batches.size()
}
}
This post series present the best of Osoco tests – tests that were tricky or we are just proud of. You can find a runnable source code for this test and more in the Grails Test Gallery project shared on GitHub.