concurrency - forkjoinpool
Buen ejemplo de Livelock? (10)
Aquí hay un ejemplo muy simple de Java de Livelock donde un esposo y esposa están tratando de comer sopa, pero solo tienen una cuchara entre ellos. Cada cónyuge es demasiado educado y pasará la cuchara si el otro aún no ha comido.
public class Livelock {
static class Spoon {
private Diner owner;
public Spoon(Diner d) { owner = d; }
public Diner getOwner() { return owner; }
public synchronized void setOwner(Diner d) { owner = d; }
public synchronized void use() {
System.out.printf("%s has eaten!", owner.name);
}
}
static class Diner {
private String name;
private boolean isHungry;
public Diner(String n) { name = n; isHungry = true; }
public String getName() { return name; }
public boolean isHungry() { return isHungry; }
public void eatWith(Spoon spoon, Diner spouse) {
while (isHungry) {
// Don''t have the spoon, so wait patiently for spouse.
if (spoon.owner != this) {
try { Thread.sleep(1); }
catch(InterruptedException e) { continue; }
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.isHungry()) {
System.out.printf(
"%s: You eat first my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
continue;
}
// Spouse wasn''t hungry, so finally eat
spoon.use();
isHungry = false;
System.out.printf(
"%s: I am stuffed, my darling %s!%n",
name, spouse.getName());
spoon.setOwner(spouse);
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner("Bob");
final Diner wife = new Diner("Alice");
final Spoon s = new Spoon(husband);
new Thread(new Runnable() {
public void run() { husband.eatWith(s, wife); }
}).start();
new Thread(new Runnable() {
public void run() { wife.eatWith(s, husband); }
}).start();
}
}
Entiendo qué es Livelock, pero me preguntaba si alguien tenía un buen ejemplo basado en el código. Y por código, no me refiero a "dos personas tratando de pasar el uno al otro en un corredor". Si vuelvo a leer eso, perderé mi almuerzo.
Codifiqué el ejemplo de 2 personas que pasaban en un pasillo. Los dos hilos se evitarán entre sí tan pronto como se den cuenta de que sus instrucciones son las mismas.
public class LiveLock {
public static void main(String[] args) throws InterruptedException {
Object left = new Object();
Object right = new Object();
Pedestrian one = new Pedestrian(left, right, 0); //one''s left is one''s left
Pedestrian two = new Pedestrian(right, left, 1); //one''s left is two''s right, so have to swap order
one.setOther(two);
two.setOther(one);
one.start();
two.start();
}
}
class Pedestrian extends Thread {
private Object l;
private Object r;
private Pedestrian other;
private Object current;
Pedestrian (Object left, Object right, int firstDirection) {
l = left;
r = right;
if (firstDirection==0) {
current = l;
}
else {
current = r;
}
}
void setOther(Pedestrian otherP) {
other = otherP;
}
Object getDirection() {
return current;
}
Object getOppositeDirection() {
if (current.equals(l)) {
return r;
}
else {
return l;
}
}
void switchDirection() throws InterruptedException {
Thread.sleep(100);
current = getOppositeDirection();
System.out.println(Thread.currentThread().getName() + " is stepping aside.");
}
public void run() {
while (getDirection().equals(other.getDirection())) {
try {
switchDirection();
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
}
Como no hay una respuesta marcada como respuesta aceptada, he intentado crear un ejemplo de bloqueo en vivo;
El programa original fue escrito por mí en abril de 2012 para aprender varios conceptos de multihilo. Esta vez lo he modificado para crear un punto muerto, una condición de carrera, un enclavamiento, etc.
Entonces, comprendamos primero la declaración del problema;
Problema del creador de cookies
Hay algunos contenedores de ingredientes: ChocoPowederContainer , WheatPowderContainer . CookieMaker toma una cierta cantidad de polvo de los contenedores de ingredientes para hornear una galleta . Si un creador de cookies encuentra un contenedor vacío, busca otro contenedor para ahorrar tiempo. Y espera hasta que Filller llene el contenedor requerido. Hay un llenador que revisa el contenedor en intervalos regulares y llena una cantidad si un contenedor lo necesita.
Por favor, compruebe el código completo en github ;
Déjame explicarte la implementación en breve.
- Comienzo Filler como hilo de daemon. Por lo tanto, seguirá llenando contenedores en intervalos regulares. Para llenar un contenedor primero, bloquea el contenedor -> verifica si necesita algo de polvo -> lo llena -> señala a todos los fabricantes que lo están esperando -> desbloquea el contenedor.
- Creo CookieMaker y establezco que puede hornear hasta 8 cookies en paralelo. Y comienzo 8 hilos para hornear galletas.
- Cada subproceso del creador crea 2 sub-subidas que se pueden llamar para tomar el polvo de los contenedores.
- Sub-thread toma un candado en un contenedor y verifica si tiene suficiente polvo. Si no, espera un tiempo. Una vez que Filller llena el contenedor, toma el polvo y desbloquea el contenedor.
- Ahora completa otras actividades como: hacer mezclas y hornear, etc.
Echemos un vistazo al código:
CookieMaker.java
private Integer getMaterial(final Ingredient ingredient) throws Exception{
:
container.lock();
while (!container.getIngredient(quantity)) {
container.empty.await(1000, TimeUnit.MILLISECONDS);
//Thread.sleep(500); //For deadlock
}
container.unlock();
:
}
IngredientContainer.java
public boolean getIngredient(int n) throws Exception {
:
lock();
if (quantityHeld >= n) {
TimeUnit.SECONDS.sleep(2);
quantityHeld -= n;
unlock();
return true;
}
unlock();
return false;
}
Todo funciona bien hasta que Filller esté llenando los contenedores. Pero si me olvido de iniciar el relleno, o si el relleno se va inesperadamente, los hilos secundarios continúan cambiando sus estados para permitir que otro fabricante revise el contenedor.
También he creado un ThreadTracer daemon que vigila los estados de los hilos y los interbloqueos. Este es el resultado de la consola;
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:RUNNABLE, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
WheatPowder Container has 0 only.
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:RUNNABLE]
2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
Notarás que subhilos y cambiando sus estados y esperando.
Dejando a un lado los comentarios imprecisos, un ejemplo conocido es el código que intenta detectar y manejar situaciones de interbloqueo. Si dos subprocesos detectan un interbloqueo, y tratan de "hacerse a un lado" el uno para el otro, sin cuidado terminarán atrapados en un bucle siempre "haciendo a un lado" y nunca logran avanzar.
Al decir "paso a un lado" me refiero a que liberarían el candado e intentarían que el otro lo adquiriera. Podríamos imaginar la situación con dos hilos haciendo esto (pseudocódigo):
// thread 1
getLocks12(lock1, lock2)
{
lock1.lock();
while (lock2.locked())
{
// attempt to step aside for the other thread
lock1.unlock();
wait();
lock1.lock();
}
lock2.lock();
}
// thread 2
getLocks21(lock1, lock2)
{
lock2.lock();
while (lock1.locked())
{
// attempt to step aside for the other thread
lock2.unlock();
wait();
lock2.lock();
}
lock1.lock();
}
Las condiciones de carrera a un lado, lo que tenemos aquí es una situación en la que ambos hilos, si entran al mismo tiempo, terminarán ejecutándose en el bucle interno sin proceder. Obviamente, este es un ejemplo simplificado. Una solución ingenua sería poner algún tipo de aleatoriedad en la cantidad de tiempo que los hilos esperarían.
La solución adecuada es respetar siempre la jerarquía de bloqueo . Elija un orden en el que adquiera las cerraduras y adhiérase a eso. Por ejemplo, si ambos hilos siempre adquieren lock1 antes que lock2, entonces no hay posibilidad de interbloqueo.
Modifico la respuesta de @jelbourn. Cuando uno de ellos se da cuenta de que el otro tiene hambre, debe soltar la cuchara y esperar otra notificación, para que se active el bloqueo.
public class LiveLock {
static class Spoon {
Diner owner;
public String getOwnerName() {
return owner.getName();
}
public void setOwner(Diner diner) {
this.owner = diner;
}
public Spoon(Diner diner) {
this.owner = diner;
}
public void use() {
System.out.println(owner.getName() + " use this spoon and finish eat.");
}
}
static class Diner {
public Diner(boolean isHungry, String name) {
this.isHungry = isHungry;
this.name = name;
}
private boolean isHungry;
private String name;
public String getName() {
return name;
}
public void eatWith(Diner spouse, Spoon sharedSpoon) {
try {
synchronized (sharedSpoon) {
while (isHungry) {
while (!sharedSpoon.getOwnerName().equals(name)) {
sharedSpoon.wait();
//System.out.println("sharedSpoon belongs to" + sharedSpoon.getOwnerName())
}
if (spouse.isHungry) {
System.out.println(spouse.getName() + "is hungry,I should give it to him(her).");
sharedSpoon.setOwner(spouse);
sharedSpoon.notifyAll();
} else {
sharedSpoon.use();
sharedSpoon.setOwner(spouse);
isHungry = false;
}
Thread.sleep(500);
}
}
} catch (InterruptedException e) {
System.out.println(name + " is interrupted.");
}
}
}
public static void main(String[] args) {
final Diner husband = new Diner(true, "husband");
final Diner wife = new Diner(true, "wife");
final Spoon sharedSpoon = new Spoon(wife);
Thread h = new Thread() {
@Override
public void run() {
husband.eatWith(wife, sharedSpoon);
}
};
h.start();
Thread w = new Thread() {
@Override
public void run() {
wife.eatWith(husband, sharedSpoon);
}
};
w.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
h.interrupt();
w.interrupt();
try {
h.join();
w.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Un ejemplo aquí podría ser el uso de un tryLock programado para obtener más de un bloqueo y si no puede obtenerlos todos, retroceda y vuelva a intentarlo.
boolean tryLockAll(Collection<Lock> locks) {
boolean grabbedAllLocks = false;
for(int i=0; i<locks.size(); i++) {
Lock lock = locks.get(i);
if(!lock.tryLock(5, TimeUnit.SECONDS)) {
grabbedAllLocks = false;
// undo the locks I already took in reverse order
for(int j=i-1; j >= 0; j--) {
lock.unlock();
}
}
}
}
Me imagino que ese código sería problemático ya que hay muchos hilos colisionando y esperando obtener un conjunto de bloqueos. Pero no estoy seguro de que esto sea muy convincente para mí como un simple ejemplo.
Un ejemplo real (aunque sin un código exacto) es el bloqueo de dos procesos en competencia en un intento de corregir un punto muerto del servidor SQL, con cada proceso utilizando el mismo algoritmo de espera-reintento para reintentar. Si bien es la suerte del momento, he visto esto en máquinas separadas con características de rendimiento similares en respuesta a un mensaje agregado a un tema EMS (por ejemplo, guardar una actualización de un solo gráfico de objetos más de una vez) y no poder controlar la orden de bloqueo.
Una buena solución en este caso sería tener consumidores en competencia (evitar el procesamiento duplicado lo más alto posible en la cadena mediante la partición del trabajo en objetos no relacionados).
Una solución menos deseable (ok, dirty-hack) es romper con anticipación la mala suerte (tipo de diferencias de fuerza en el procesamiento) o romperla después de un punto muerto utilizando diferentes algoritmos o algún elemento de aleatoriedad. Esto aún podría tener problemas porque es posible que el orden de bloqueo sea "adherente" para cada proceso, y esto requiere un tiempo mínimo determinado que no se tiene en cuenta en el tiempo de espera.
Otra solución más (al menos para SQL Server) es probar un nivel de aislamiento diferente (por ejemplo, instantánea).
Versión C # del código de jelbourn:
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace LiveLockExample
{
static class Program
{
public static void Main(string[] args)
{
var husband = new Diner("Bob");
var wife = new Diner("Alice");
var s = new Spoon(husband);
Task.WaitAll(
Task.Run(() => husband.EatWith(s, wife)),
Task.Run(() => wife.EatWith(s, husband))
);
}
public class Spoon
{
public Spoon(Diner diner)
{
Owner = diner;
}
public Diner Owner { get; private set; }
[MethodImpl(MethodImplOptions.Synchronized)]
public void SetOwner(Diner d) { Owner = d; }
[MethodImpl(MethodImplOptions.Synchronized)]
public void Use()
{
Console.WriteLine("{0} has eaten!", Owner.Name);
}
}
public class Diner
{
public Diner(string n)
{
Name = n;
IsHungry = true;
}
public string Name { get; private set; }
private bool IsHungry { get; set; }
public void EatWith(Spoon spoon, Diner spouse)
{
while (IsHungry)
{
// Don''t have the spoon, so wait patiently for spouse.
if (spoon.Owner != this)
{
try
{
Thread.Sleep(1);
}
catch (ThreadInterruptedException e)
{
}
continue;
}
// If spouse is hungry, insist upon passing the spoon.
if (spouse.IsHungry)
{
Console.WriteLine("{0}: You eat first my darling {1}!", Name, spouse.Name);
spoon.SetOwner(spouse);
continue;
}
// Spouse wasn''t hungry, so finally eat
spoon.Use();
IsHungry = false;
Console.WriteLine("{0}: I am stuffed, my darling {1}!", Name, spouse.Name);
spoon.SetOwner(spouse);
}
}
}
}
}
Versión de Python del código de jelbourn:
import threading
import time
lock = threading.Lock()
class Spoon:
def __init__(self, diner):
self.owner = diner
def setOwner(self, diner):
with lock:
self.owner = diner
def use(self):
with lock:
"{0} has eaten".format(self.owner)
class Diner:
def __init__(self, name):
self.name = name
self.hungry = True
def eatsWith(self, spoon, spouse):
while(self.hungry):
if self != spoon.owner:
time.sleep(1) # blocks thread, not process
continue
if spouse.hungry:
print "{0}: you eat first, {1}".format(self.name, spouse.name)
spoon.setOwner(spouse)
continue
# Spouse was not hungry, eat
spoon.use()
print "{0}: I''m stuffed, {1}".format(self.name, spouse.name)
spoon.setOwner(spouse)
def main():
husband = Diner("Bob")
wife = Diner("Alice")
spoon = Spoon(husband)
t0 = threading.Thread(target=husband.eatsWith, args=(spoon, wife))
t1 = threading.Thread(target=wife.eatsWith, args=(spoon, husband))
t0.start()
t1.start()
t0.join()
t1.join()
if __name__ == "__main__":
main()
package concurrently.deadlock;
import static java.lang.System.out;
/* This is an example of livelock */
public class Dinner {
public static void main(String[] args) {
Spoon spoon = new Spoon();
Dish dish = new Dish();
new Thread(new Husband(spoon, dish)).start();
new Thread(new Wife(spoon, dish)).start();
}
}
class Spoon {
boolean isLocked;
}
class Dish {
boolean isLocked;
}
class Husband implements Runnable {
Spoon spoon;
Dish dish;
Husband(Spoon spoon, Dish dish) {
this.spoon = spoon;
this.dish = dish;
}
@Override
public void run() {
while (true) {
synchronized (spoon) {
spoon.isLocked = true;
out.println("husband get spoon");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
if (dish.isLocked == true) {
spoon.isLocked = false; // give away spoon
out.println("husband pass away spoon");
continue;
}
synchronized (dish) {
dish.isLocked = true;
out.println("Husband is eating!");
}
dish.isLocked = false;
}
spoon.isLocked = false;
}
}
}
class Wife implements Runnable {
Spoon spoon;
Dish dish;
Wife(Spoon spoon, Dish dish) {
this.spoon = spoon;
this.dish = dish;
}
@Override
public void run() {
while (true) {
synchronized (dish) {
dish.isLocked = true;
out.println("wife get dish");
try { Thread.sleep(2000); } catch (InterruptedException e) {}
if (spoon.isLocked == true) {
dish.isLocked = false; // give away dish
out.println("wife pass away dish");
continue;
}
synchronized (spoon) {
spoon.isLocked = true;
out.println("Wife is eating!");
}
spoon.isLocked = false;
}
dish.isLocked = false;
}
}
}