For buffered channel approach the reader may refer to this article. This time we dicuss another native API from kotlin called Semaphore.
import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit class SomeConcurrenTask( private val transactionTemplate: TransactionTemplate, ) { // divide tasks into batches with each being of size at most 20 val taskIdsBatches = taskIds.chunked(20) // concurrnet limit to 10 val semaphore = Semaphore(10) runBlocking { val deferredResults = taskIdsBatches.mapIndexed { batchIndex, taskIdsBatch -> async(Dispatchers.IO) { semaphore.withPermit { println("Processing batch $batchIndex/${taskIdsBatches.size}") transactionTemplate.execute { someTransaction(taskIdsBatch) } } } } println("Waiting for everything to finish ...") deferredResults.awaitAll() println("Finished!") } }