[La entrevista debe saber] Varias formas de implementar el modelo productor / consumidor en Kotlin

Inserte la descripción de la imagen aquí

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)

  1. Sincronizado
  2. Bloquear
  3. BlockingQueue
  4. Semáforo
  5. RxJava
  6. 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.

Supongo que te gusta

Origin blog.csdn.net/vitaviva/article/details/108556523
Recomendado
Clasificación