Hay muchas formas de implementar un modelo de producción / consumo de múltiples subprocesos en Kotlin (la mayoría de ellas también se aplican a Java)
- Sincronizado
- Bloquear
- BlockingQueue
- Semáforo
- RxJava
- Corutina
Sincronizado
val buffer = LinkedList<Data>()
val MAX = 5 //buffer最大size
val lock = Object()
fun produce(data: Data) {
sleep(2000) // mock produce
synchronized(lock) {
while (buffer.size >= MAX) {
// 当buffer满时,停止生产
// 注意此处使用while不能使用if,因为有可能是被另一个生产线程而非消费线程唤醒,所以要再次检查buffer状态
// 如果生产消费两把锁,则不必担心此问题
lock.wait()
}
buffer.push(data)
// notify方法只唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。
// notifyAll会唤醒所有等待中线程,哪一个线程将会第一个处理取决于操作系统的实现,但是都有机会处理。
// 此处使用notify有可能唤醒的是另一个生产线程从而造成死锁,所以必须使用notifyAll
lock.notifyAll()
}
}
fun consume() {
synchronized(lock) {
while (buffer.isEmpty())
lock.wait() // 暂停消费
buffer.removeFirst()
lock.notifyAll()
}
sleep(2000) // mock consume
}
// 同时启动多个生产、消费线程
repeat(10) {
Thread {
produce(Data()) }.start()
}
repeat(10) {
Thread {
consume() }.start()
}
Bloquear
La ventaja de Lock over Synchronized es que cuando hay múltiples líneas de producción / subprocesos de consumo, podemos especificar con precisión cuál activar mediante la definición de múltiples condiciones. El siguiente ejemplo no es tan complicado, simplemente reemplace el texto sincronizado anterior
val buffer = LinkedList<Data>()
val MAX = 5 //buffer最大size
val lock = ReentrantLock()
val condition = lock.newCondition()
fun produce(data: Data) {
sleep(2000) // mock produce
lock.lock()
while (buffer.size >= 5)
condition.await()
buffer.push(data)
condition.signalAll()
lock.unlock()
}
fun consume() {
lock.lock()
while (buffer.isEmpty())
condition.await()
buffer.removeFirst()
condition.singleAll()
lock.unlock()
sleep(2000) // mock consume
}
BlockingQueue
Cuando BlockingQueue alcanza una condición crítica, la lectura y la escritura bloquearán automáticamente el hilo actual esperando la liberación del bloqueo, que es naturalmente adecuado para este escenario de producción / consumo
val buffer = LinkedBlockingQueue<Data>(5)
fun produce(data: Data) {
sleep(2000) // mock produce
buffer.put(data) //buffer满时自动阻塞
}
fun consume() {
buffer.take() // buffer空时自动阻塞
sleep(2000) // mock consume
}
Semáforo
El semáforo es un mecanismo de bloqueo compartido proporcionado por JUC, que puede controlar la congestión. Esta función se puede utilizar para controlar el tamaño del búfer.
// canProduce: 可以生产数量(即buffer可用的数量),生产者调用acquire,减少permit数目
val canProduce = Semaphore(5)
// canConsumer:可以消费数量,生产者调用release,增加permit数目
val canConsume = Semaphore(5)
// 控制buffer访问互斥
val mutex = Semaphore(0)
val buffer = LinkedList<Data>()
fun produce(data: Data) {
if (canProduce.tryAcquire()) {
sleep(2000) // mock produce
mutex.acquire()
buffer.push(data)
mutex.release()
canConsume.release() //通知消费端新增加了一个产品
}
}
fun consume() {
if (canConsume.tryAcquire()) {
sleep(2000) // mock consume
mutex.acquire()
buffer.removeFirst()
mutex.release()
canProduce.release() //通知生产端可以再追加生产
}
}
RxJava
RxJava no es adecuado para escenarios de múltiples productores y consumidores, pero puede usarse en escenarios de un solo productor / consumidor.
El mecanismo de compresión de Flowable se puede utilizar para controlar la cantidad de búferes
class Producer : Flowable<Data>() {
override fun subscribeActual(subscriber: org.reactivestreams.Subscriber<in Data>) {
subscriber.onSubscribe(object : Subscription {
override fun cancel() {
//...
}
private val outStandingRequests = AtomicLong(0)
override fun request(n: Long) {
//收到下游通知,开始生产
outStandingRequests.addAndGet(n)
while (outStandingRequests.get() > 0) {
sleep(2000)
subscriber.onNext(Data())
outStandingRequests.decrementAndGet()
}
}
})
}
}
class Consumer : DefaultSubscriber<Data>() {
override fun onStart() {
request(1)
}
override fun onNext(i: Data?) {
sleep(2000) //mock consume
request(1) //通知上游可以增加生产
}
override fun onError(throwable: Throwable) {
//...
}
override fun onComplete() {
//...
}
}
@Test
fun test_rxjava() {
try {
val testProducer = Producer)
val testConsumer = Consumer()
testProducer
.subscribeOn(Schedulers.computation())
.observeOn(Schedulers.single())
.blockingSubscribe(testConsumer)
} catch (t: Throwable) {
t.printStackTrace()
}
}
Continuar / producir
El Canal en la corrutina tiene un mecanismo de control de congestión, que puede realizar la comunicación entre productores y consumidores.
La encapsulación oficial del método de producción basado en Channel, específicamente para hacer frente a tales escenarios.
val produce = produce(Dispatchers.IO, 5) {
repeat(10) {
sleep(2000) //mock produce
val data = Data()
send(data) // 超过5个以上没有消费完时,协程挂起
}
}
produce.consumeEach {
sleep(3000) //mock consume
}
Resumen
El problema productor-consumidor del hilo no es más que manejar la comunicación entre los dos extremos.Los candados, semáforos, contrapresión RxJava y canales de rutina son todos métodos de comunicación alternativos.