Concurrence

Producteur/Consommateur


Producteur/Consommateur

Producteur/Consommateur est un design pattern qui permet de faire communiquer :

  • 1 ou plusieurs threads produisant des données,
  • 1 ou plusieurs threads traitant/consommant ces données.

La difficulté est de pouvoir:

  • bloquer les producteurs quand les consommateurs n'arrivent pas à traiter les données.
  • bloquer les consommateurs quand il n'y a pas de données.

Situation

Solution

Lorsque la file est pleine, les producteurs restent bloqués en attentant que la file se vide.

La file bloquante est le seul mécanisme de synchronisation utilisé ici !

BlockingQueue

La file des producteurs/consommateurs est déjà implantée en Java. Il existe plusieurs implémentations de l'interface BlockingQueue.

  • ArrayBlockingQueue :
    Elle utilise un tableau circulaire (comme ArrayDeque).
  • LinkedBlockingQueue :
    Elle utilise une liste chainée (attention par défaut la taille n'est pas fixée).
  • SynchronousQueue :
    Celle-ci n'accepte qu'un seul élement à la fois et ne le stocke pas. On ne peut pas la parcourir.

BlockingQueue

Extrait de la doc Java:

Throws exception Special value Blocks Times out
Insert add(e) offer(e) put(e) offer(e, time, unit)
Remove remove() poll() take() poll(time, unit)
Examine element() peek() - -

Dans le contexte du pattern producteur/consommateur, on n'utilise jamais les méthodes levant une exception ; et très rarement celles renvoyant une valeur spéciale.

Exemple

BlockingQueue<String> queue = new ... // quelle implémentation ?
for (var i = 0; i < 3; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(100);
                queue.put(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}
for (var i = 0; i < 2; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(500);
                System.out.println("next " + queue.take());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}

Mauvais choix de conception

BlockingQueue<String> queue = new ...
for (var i = 0; i < 3; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(100);
                queue.put(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}
for (var i = 0; i < 2; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(500);
                if ( !queue.isEmpty() ){
                    System.out.println("next : " + queue.remove()); // idem avec queue.poll()
                }
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}

Mauvais choix de BlockingQueue

var queue = new LinkedBlockingQueue<String>();
for (var i = 0; i < 3; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(100);
                queue.put(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}
for (var i = 0; i < 2; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(500);
                System.out.println("next : " + queue.take());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}

La file va grandir jusqu'à faire une OutOfMemoryError.

Exemple correct

var queue = new ArrayBlockingQueue<String>(10);
for (var i = 0; i < 3; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(100);
                queue.put(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}
for (var i = 0; i < 2; i++) {
    Thread.ofPlatform().start(() -> {
        for(;;) {
            try {
                Thread.sleep(500);
                System.out.println("next : " +  queue.take());
            } catch (InterruptedException e) {
                return;
            }
        }
    });
}

Plus de possiblité de OutOfMemoryError.