Concurența și programarea obiect-orientată

Jan 16th, 2011 | By | Category: Featured, Paralelism și concurență, Proiectare și programare

În cazul unor sisteme informatice ce includ activități concurente se acutizează efectele de dispersie a funcționalităților și a codului confuz. Sincronizarea și comunicarea inerentă acestor sisteme face elaborarea de software dificilă implicând dificultăţi de reutilizare din cauza unor conflicte dintre codul de implementare a funcționalităților de bază și a funcționalităților de organizare a concurenței (sincronizare, planificare, protecție, etc.). Aceste conflicte au fost intens studiate și le găsim în lucrări de specialitate cu numele de anomalii de moștenire (inheritance anomaly ).

Mai mulţi cercetători din domeniu (Satoshi Matsuoka, Akinori Yonezawa în [1]; Lobel Crnogorac în [2]; Denis Kafura, Greg Lavender în [3]; și alții) au încercat să clasifice și să formalizeze aceste anomalii ce fac nejustificată sau chiar imposibilă reutilizarea prin moștenire a claselor de bază, căci implică redefinirea a unui număr prea mare de metode. Aceleaşi anomalii le regăsim menționate și în relativ mai recenta lucrare [4] (din 2004, autori fiind Giuseppe Milicia și Vladimiro Sassone), în care se susține că nici limbajele populare moderne de programare nu exclud apariția anomaliilor.

Conform lucrărilor menționate se disting trei clase generice pentru anomalii în care conceptul moștenirii ca formă de reutilizare este mult diminuat:

  • Anomalii determinate de istoric (history-sensitive anomaly – l. eng.);
  • Anomalii de partiționare a stărilor abstracte (partitioning of states – l. eng.);
  • Anomalii de modificare a stărilor abstracte (modification of acceptable states – l. eng.).

Anomalia determinată de istoric ne poate oferi un exemplu clasic în care moștenirea își pierde din esența sa pentru reutilizare, căci în clasa derivată sunt introduse noi operații care impune cerințe noi de sincronizare, ce pot forţa redefinirea celorlalte operaţii [4].

Fie clasa BoundedBuffer ce reprezintă o stivă de o capacitate limitată asupra căreia pot fi efectuate două operații: get() – preluare element din stivă și put(Object element) – plasare element în stivă. Constrângerile de sincronizare sunt realizate prin gardarea metodelor, ce presupune evaluarea unei expresii logice. Firul de execuție pentru care expresia dată se va dovedi a fi false va fi „impus” să aștepte evaluarea expresiei în true [5].

public class BoundedBuffer {

protected Object[] buf;
protected int MAX;
protected int current = 0;

Buffer(int max) {
  MAX = max;
  buf = new Object[MAX];
}

public synchronized Object get() throwsException {
  while (current<=0) { wait(); }
  current--;
  Object ret = buf[current];
  notifyAll();
  return ret;
}

public synchronized void put(Object v)  throws Exception {
  while(current>=MAX) { wait(); }
  buf[current] = v;
  current++;
  notifyAll();
}

}

Figura 1.1 – Clasa BoundedBuffer în limbajul Java [5]

Să presupunem că se dorește specializarea clasei BoundedBuffer prin adăugarea unei noi operații gget() ce se manifestă întocmai ca și metoda get(), doar că nu poate fi apelată și executată imediat după get(). Astfel suntem nevoiți să redefinim întreaga clasă BoundedBuffer, căci avem de adăugat cod în get() și în put() pentru a „reține” dacă a avut loc sau nu invocarea metodei get() (Figura 1.2).

public class HistoryBuffer extends BoundedBuffer {

boolean afterGet = false;
public HistoryBuffer(int max) { super(max); }

public synchronized Object gget() throws Exception {
  while ( (current <= 0) || afterGet ) {
    wait();
  }
  afterGet = false;
  return super.get();
}

public synchronized Object get() throws Exception {
  Object o = super.get();
  afterGet = true;
  return o;
}
public synchronized void put(Object v) throws Exception
{
  super.put(v);
  afterGet = false;
}

}

Figura 1.2 – Anomalia determinată de istoric în clasa HistoryBuffer [5]

Pentru descrierea anomaliei de partiționare a stărilor abstracte este nevoie să definim comportamentul unui obiect BoundedBuffer prin trei stări abstracte posibile: empty, partial și full. Astfel dacă starea obiectului devine empty se inhibă apelul metodei get(). În mod firesc, nu poate fi apelată metoda put(), dacă starea obiectului este full.

Să admitem că se dorește specializarea clasei BoundedBuffer prin adăugarea unei noi operații, get2(), care să preia simultan două elemente din stivă. Astfel se cere reevaluarea a ceea ce reprezintă, cel puțin, starea empty, căci este necesar să distingem situația când în stivă este doar un element. Dacă analizăm starea partial, se va impune încă o stare partial2, când în stivă avem cel puțin două elemente. Tocmai această nouă substare ne va permite execuția metodei get2(). Este evident că realizarea noii operațiuni poate implica și modificarea celorlalte.

Anomalia de modificare a stărilor abstracte este determinată de faptul că adăugarea unei noi operații poate forța modificarea altor operații, ce nu ar fi fost necesară, dacă noua operație nu ar fi. Exemplul pentru descrierea acestei anomalii este comun pentru majoritatea lucrărilor în care se menționează problema anomalilor de moștenire și constă în specializarea clasei BoundedBuffer prin adăugarea unei posibilități de blocare a plasării și preluării din stivă [1,2,4,5,6]. Astfel starea unui obiect va depinde nu doar de numărul de elemente, dar și de valoarea unui nou atribut boolean locked. Acest fapt determină să redefinim complet operațiile get() și put() în noua clasă LockedBuffer (Figura 1.3).

public class LockedBuffer extends BoundedBuffer {

boolean locked = false;
public LockedBuffer(int max) { super(max); }

public synchronized void lock() throws Exception {
  if(!locked) locked = true;
}

public synchronized void unLock() throws Exception {
  if(locked) locked = false;
}

public synchronized Object get() throws Exception {
  while (current<=0 || !locked) { wait(); }
  current--;
  Object ret = buf[current];
  notifyAll();
  return ret;
}

public synchronized void put(Object v) throws Exception
  while (current>=MAX || !locked) { wait(); }
  buf[current] = v;
  current++;
  notifyAll();
}

}

Figura 1.3 – Anomalia de modificare a stărilor abstracte în clasa LockedBuffer

În teza sa de doctor Dan Suciu propune tratarea unitară a fenomenelor printr-o denumire generală – anomalii de reutilizare, argumentând că într-un context concurent efectele menționate mai sus pot avea loc și în cazul relațiilor de delegare, agregare și asociere [6]. Acest fapt este uşor susţinut şi în alte lucrări. Un exemplu poate servi lucrarea [7] de Lodewijk Bergmans și Mehmet Aksit, care afirmă că fenomene nefaste de reutilizare pot apărea și în alte cazuri decât al moștenirilor, referindu-se la anomaliile de compoziție (composition anomaly).

Pentru investigarea fenomenului discutat ce apare la reutilizarea codului în contextul concurenței în lucrarea [2] s-a recurs la formalizarea anomaliilor. Rezultatele analizei formale prezentate în lucrare a surprins puțin comunitatea științifică. Ușor se remarcă următoarele enunțuri care ies în evidență dintr-un conținut mai larg al cercetării:

  • Noțiunea de tip al unei clase în mod firesc trebuie să cuprindă și toate versiunile comportamentale concurente ale aceleiași clase, menținându-le separate, în scopul reutilizării ulterioare (acest fapt este oarecum aproape de cercetările Barbarei Liskov referitoare subtipizării comportamentale [8]);
  • Anomaliile de moștenire sunt comune și altor paradigme decât programării obiect-orientate: de exemplu programării orientate pe agenți (fundamentată pe modelul Actor [9,10,11]);
  • Anomalia fiind prezentă în implementare, nu în mod obligatoriu, cauzează probleme practice (în mod cert putem identifica anomalii și într-un context secvențial al programării obiect-orientate);
  • Problema anomaliilor de moștenire într-o formă sau alta încă nu poate fi rezolvată, ci mai degrabă diminuate efectele nefaste induse de anomalii.

Bibliografie selectată

1. Matsuoka, Satoshi și Yonezawa, Akinori. Analysis of Inheritance Anomaly in Object-Oriented Concurrent Programming Languages. Research Directions in Concurrent Object-Oriented Programming. s.l. : MIT Press, 1993.
2. Crnogorac, Lobel, Rao, Anand S. și Ramamohanarao, Kotagiri. Classifying Inheritance Mechanisms in Concurrent Object-Oriented Programming. Proceedings of ECOOP’98. s.l. : Springer-Verlag, 1998. LNCS 1445.
3. Kafura, Dennis G. și Lavender, Greg. Concurrent Object-Oriented Languages and the Inheritance. In Proceedings of ISIPCALA’93. 1993.
4. Milicia, Giuseppe și Sassone, Vladimiro. The inheritance anomaly: ten years after. Proceedings of Symposium on Applied Computing (SAC). Nicosia, Cyprus : ACM, 2004.
5. Wisnesky, Ryan J. The Inheritance Anomaly Revisited. [http://www.eecs.harvard.edu/~ryan/anomaly.pdf] August 2006.
6. Suciu, Dan Mircea. Tehnici de implementare a concurenței în analiza și proiectarea orientată pe obiecte. Teză de doctor. [http://cs.ubbcluj.ro/~tzutzu/Research/Teza.pdf]. Cluj-Napoca : Universitatea Babeș-Boleay, 2001.
7. Bergmans, Lodewijk și Aksit, Mehmet. Composing Software from Multiple Concerns: A Model and Composition Anomalies. Proceedings of ICSE 2000 (2nd) Workshop on Multidimensional Separation of Concerns. 2000.
8. Liskov, Barbara și Wing, Jeannette M. A Behavioral Notion of Subtyping. ACM Transactions on Programming Languages and Systems (TOPLAS). New York, USA : ACM, 1994. Vol. 16, 6. ISSN: 0164-0925.
9. Agha, Gul. Actors: A Model of Concurrent Computation in Distributed Systems. Teză doctor. Cambridge : MIT Press, 1986. AITR-844.
10. Agha, Gul și Thati, Prasanna. An Algebraic Theory of Actors and Its Application to a Simple Object-Based Language. Formal Methods and Declarative Languages Laboratory. [Interactiv] 2004. [Citat: 26 Noiembrie 2010.] http://formal.cs.uiuc.edu/papers/ATactors_festschrift.pdf. LNCS 2635.
11. Hewitt, Carl. Actor Model of Computation. arXiv.org. [Interactiv] 08 Noimbrie 2010. [Citat: 26 Noimbrie 2010.] http://arxiv.org/abs/1008.1459v8. arXiv:1008.1459v8.

Tags: