Skip to content Skip to navigation

Connexions

You are here: Home » Content » Thread e I/O non bloccante

Navigation

Recently Viewed

This feature requires Javascript to be enabled.

Thread e I/O non bloccante

Module by: Davide Rocchesso. E-mail the author

User rating (How does the rating system work?)
Ratings

Ratings allow you to judge the quality of modules. If other users have ranked the module then its average rating is displayed below. Ratings are calculated on a scale from one star (Poor) to five stars (Excellent).

How to rate a module

Hover over the star that corresponds to the rating you wish to assign. Click on the star to add your rating. Your rating should be based on the quality of the content. You must have an account and be logged in to rate content.

:
(0 ratings)

Summary: A brief introduction to Java Threads as accessible from Processing.

Thread: definizione

Un thread (di controllo) è una lista di istruzioni eseguite sequenzialmente da un programma. I thread di un programma condividono uno stesso spazio di indirizzamento. Pur avendo stack e variabili locali separate, condividono le variabili globali. I thread sono "leggeri", nel senso che la creazione, distruzione e sincronizzazione sono relativamente economiche grazie alla condivisione dello spazio di indirizzamento. Le ragioni per organizzare un programma in un certo numero di thread possono essere molteplici:

  • Certi programmi si scrivono più semplicemente, in special modo le collezioni di compiti debolmente connessi (cioè largamente indipendenti).
  • I programmi interattivi risultano più efficienti laddove il servizio dell'input o il display dell'output sono organizzati in thread distinti.
  • I programmi sono potenzialmente parallelizzabili su architetture multi-processore o multi-core.
  • Il problema in esame richiede parti di programma in comunicazione asincrona tra loro.
  • E' utile imporre una struttura modulare al codice.

I/O non bloccante

Una delle motivazioni forti per la programmazione concorrente mediante thread è l'ottenimento di servizi non bloccanti per Input/Output. Quando l'applicazione ha necessità di effettuare un I/O, è opportuno che non si blocchi, in modo da consentire che altre operazioni non dipendenti da quell'I/O possano essere effettuate. Una tecnica di gestione dell'I/O non bloccante, utilizzata ad esempio nei microcontrollori usati nelle board per il physical computing, è il polling, cioè la verifica ciclica dell'accadimento di eventi su un insieme di dispositivi di input. La ciclicità del polling è gestita da un timer. L'I/O non bloccante si può realizzare mediante i thread. La lettura di un certo dispositivo si può assegnare ad un certo thread il quale si blocca in attesa dei dati. Gli altri thread, che non dipendono dalla lettura del dato, possono però procedere in maniera concorrente.

Diagrammi di attivazione

Il codice Processing seguente invoca il metodo stampa() sull'oggetto cl della classe Classe1.



class Classe1 {
 void stampa() {
   for (int i=0; i< 100; i++) println("yep");
 } 
}

void setup() {
 Classe1 cl = new Classe1();
 cl.stampa(); 
}

	
Il flusso di elaborazione si può rappresentare graficamente con il diagramma di attivazione di Figura 1, il quale presenta evidentemente un singolo thread.
Figura 1: Diagramma di attivazione a thread singolo
Figura 1 (atti1.png)
Se invece la classe viene realizzata come estensione della classe Thread, allora è possibile procedere all'attivazione di un thread secondario mediante invocazione del metodo start(). Il codice va riscritto come


class Classe2 extends Thread{
 void run() {
   for (int i=0; i< 100; i++) println("yep");
 } 
}

void setup() {
 Classe2 cl = new Classe2();
 cl.start(); 
}

	
Si noti che il metodo stampa() ora si chiama run(). Il metodo start() esiste nelle superclassi di Classe2 (nella classe Thread, e si occupa dell'invocazione del metodo run(). Questa volta il flusso di elaborazione, riportato in Figura 2 presenta due thread.
Figura 2: Diagramma di attivazione a doppio thread
Figura 2 (atti2.png)

Nota:

Esiste un secondo modo di dichiarare e attivare un thread, mediante implementazione della interfaccia Runnable di Java. E' questa modalità che bisogna usare se si vuole attivare un nuovo thread su un oggetto di una classe che è già dichiarata come estensione di un'altra classe.

Thread: utilizzazione

Esempio 1: Thread temporizzati

La classe Thread possiede un metodo sleep() che la mette in stato blocked per un certo numero di millisecondi. Ciò consente di programmare in maniera compatta e agevole flussi di eventi tra loro indipendenti. Ad esempio, il codice che segue produce il disegno di ellissi di due colori diversi, e ogni colore corrisponde ad un diverso intervallo di tempo (i.e., 1000 e 1300 millisecondi) tra due eventi successivi di disegno.



class TimerThread extends Thread{
  int timediff; // quanto temporale
  color col;  
  
  TimerThread(color c, int td) {
   timediff = td;
   col = c;
  }
  
 void run() {
   while(true) {
     fill(col);
     try {
       ellipse(int(random(100)), int(random(100)), 
         int(random(20)), int(random(20)));  
       sleep(timediff);
     } catch (Exception e) {println("Exception in sleep");}
   }
 } 
}

void setup() {
 TimerThread tt1 = new TimerThread(color(120,120,0),1000);
 TimerThread tt2 = new TimerThread(color(0,120,120),1300);
 tt1.start(); 
 tt2.start();
}

void draw() {
}


	
Si nota che il metodo sleep() di Java esige di essere invocato all'interno di un costrutto try catch(), cioè di una sezione di codice che consenta la cattura delle eccezioni. La gestione delle eccezioni consente di affrontare delle condizioni che alterano il normale flusso di esecuzione di un programma. Nell'esempio, la sleep() può fallire e sollevare (throw) una eccezione che, in questo caso, è gestita mediante la mera scrittura di una messaggio sulla console.

Exercise 1

Si aggiunga al codice di Esempio 1 una classe che si occupa di ridipingere a intervalli regolari lo sfondo, in maniera da evitare la sovrapposizione delle ellissi.

I/O non bloccante basato su thread

Quando l'oggetto interattivo ha bisogno di leggere o scrivere da/su file, dispositivi, o network socket, è opportuno che essa non si blocchi completamente in attesa dei dati. Con i thread, ciò viene risolto elegantemente attivando un thread separato che gestisce I/O asincrono, e si blocca laddove è necessario.

Esempio 2: Lettura di comandi da tastiera

Si supponga di dover disegnare in maniera automatica e ripetuta delle ellissi nella finestra grafica, e di voler controllare gli attributi di tali ellissi mediante parole chiave immesse da tastiera. Conviene separare il compito di lettura e interpretazione (parsing) del flusso di caratteri che viene dalla tastiera dal compito di produzione dell'output grafico. Ciò si può realizzare come segue, nel caso semplice in cui le parole accettate siano "rosso", "verde", e "blu" corrispondenti a diverse colorature delle ellissi prodotte.



StringBuffer stdin;
boolean linea;
color colore;

void keyReleased() {
  char c = key;
  if (c!='\n') {
    stdin.append(c);
  }
  else linea=true;
}

class ColorInput extends Thread {
  String results;
  char c;
 
  void run()  {
     while(true) {
       if (linea) {
            println(stdin);
            results=stdin.toString();
            stdin.setLength(0);	    
            linea = false;
            if (results.equals("rosso")) {
                colore = color(255, 0, 0);
            }
             if (results.equals("verde")) {
                colore = color(0, 255, 0);
            }
            if (results.equals("blu")) {
                colore = color(0, 0, 255);
            }          
          }
         try {
         sleep(5); // to relief the cpu from active waiting
         } catch (Exception e) {println("Exception in sleep");} 
        }
    }
  }

class TimerThread extends Thread{
  int timediff; // quanto temporale
  
  TimerThread(int td) {
   timediff = td;
  }
  
 void run() {
   while(true) {
     try {   
       fill(colore);
       ellipse(int(random(100)), int(random(100)), 
         int(random(20)), int(random(20)));  
       sleep(timediff);
     } catch (Exception e) {println("Exception in sleep");}
   }
 } 
}

void setup() {
 stdin = new StringBuffer();
 TimerThread tt1 = new TimerThread(100);
 ColorInput ci = new ColorInput();
 ci.start();
 tt1.start(); 
}

void draw() {
}


	  
Sono presenti, in questo caso, due diverse estensioni della classe Thread. La prima estensione fa una attesa attiva di linee di testo, impostando il colore ogniqualvolta viene rilevata una linea di testo contenente una delle tre parole chiave riconosciute. L'invocazione di sleep(5) rende questa attesa attiva meno onerosa per la CPU. L'altro thread, invece, si occupa di disegnare dieci ellissi al secondo. In Processing è difficile realizzare un input bloccante da tastiera, in quanto non è accessibile direttamente lo stream System.in, sul quale in Java si può normalmente applicare lettura bufferizzata bloccante.

Nota:

Per una introduzione all'I/O in Java si veda il Java Java IO Tutorial.
Viceversa, Processing invita ad una programmazione event-based fornendo gli event handler keyReleased(), keyPressed(), e keyTyped(). E' possibile però fare un input bloccante di una linea da file di testo con un codice del tipo

try {
   BufferedReader stdiin = createReader("nomefile");
   println(stdiin.readLine());
 }catch(Exception e){}
	  
dove createReader() è una funzione di Processing che crea un BufferedReader object da un file o da una URL. Essa consente una leggera semplificazione rispetto al codice Java

try {
   FileReader is = new FileReader("nomefile");
   BufferedReader stdiin = new BufferedReader( is );
   println(stdiin.readLine());
 }catch(Exception e){}
	  
Per semplificare la lettura da file di testo, locali o remoti, Processing mette a disposizione la funzione loadStrings(), che carica tutte le linee di file di testo in un array di tipo String[]. Questo metodo può essere utile se il file non è troppo grande o dinamicamente variabile.

Exercise 2

Il codice che segue effettua la lettura periodica di una linea di testo da un sito generatore di testo. Lo si estenda aggiungendo un thread che visualizza queste frasi sulla finestra grafica con animazione tipografica.



class sentenceReader extends Thread{
  int timediff; // quanto temporale
  
  sentenceReader(int td) {
   timediff = td;
  }
  
 void run() {
    while(true) {
      try {
       BufferedReader stdiin = createReader("http://www.essl.at/cgi-bin/swrap/cgis/lexikon-orakel.pl");
       for (int i=0; i<13; i++) stdiin.readLine();
       println(stdiin.readLine());
       sleep(timediff);
      }catch(Exception e){}
    }
  }
}

void setup() {
 sentenceReader st = new sentenceReader(2000);
 st.start();
}

void draw() {
}


	    

Sincronizzazione

Si supponga di dover estendere la lettura di comandi da tastiera con due thread che prendono comandi da stdin. I comandi possono essere consumati dall'uno o dall'altro dei thread, ma uno stesso comando non può essere consumato da entrambi. In questo caso si possono presentare problemi di race condition, cioè configurazioni di codice concorrente che danno luogo a risultati dipendenti dalla sequenza di scheduling attribuita ai vari thread. In particolare, la sezione critica


	    results=stdin.toString();            
            stdin.setLength(0);
            linea = false;
	
può dare luogo a comportamenti inconsistenti se interrotta dallo scheduler per passare il controllo dall'uno all'altro thread. Questi problemi si possono risolvere imponendo la non interrompibilità delle sezioni critiche di codice, per mezzo della parola chiave synchronized. Nella fattispecie, si può introdurre una classe con metodi sincronizzati:


class IO extends Thread {
 String results;

 synchronized  String acquire() {
   results=stdin.toString();
   linea = false;
   stdin.setLength(0);
   return(results);
 }
}

	
I metodi invocati su un oggetto di questa classe non sono interrompibili da metodi invocati sullo stesso oggetto. In ambito di transazioni bancarie, per esempio, un metodo prelievo() dovrà essere sincronizzato per evitare inconsistenze in presenza di prelievi multipli, come possono essere effettuati da co-titolari dello stesso conto. La sincronizzazione di thread è un argomento complesso, per l'approfondimento del quale esistono libri specializzati.

Exercise 3

Si estenda la lettura di comandi da tastiera con due thread che prendono comandi da stdin.

Solution

Nel codice seguente si noti l'uso della getName() per stampare il nome del thread interessato.



StringBuffer stdin;
boolean linea;
color colore;
IO io = new IO();

void keyReleased() {
  char c = key;
  if (c!='\n') {
    stdin.append(c);
  }
  else linea=true;
}

class IO extends Thread {
 String results;

 synchronized  String acquire() {
   results=stdin.toString();
   linea = false;
   stdin.setLength(0);
   return(results);
 }
}

class ColorInput extends Thread {
  String results;
  char c;
 
  void ColorInput (IO inout) {
    io = inout;
  }
  void run()  {
     while(true) {
       if (linea) {
            results=io.acquire();   
            println(this.getName() + results);
            if (results.equals("rosso")) {
                colore = color(255, 0, 0);
            }
             if (results.equals("verde")) {
                colore = color(0, 255, 0);
            }
            if (results.equals("blu")) {
                colore = color(0, 0, 255);
            }          
          }
         try {
         sleep(2); // to relief the cpu from active waiting
         } catch (Exception e) {println("Exception in sleep");} 
        }
    }
  }

class TimerThread extends Thread{
  int timediff; // quanto temporale
  
  TimerThread(int td) {
   timediff = td;
  }
  
 void run() {
   while(true) {
     try {   
       fill(colore);
       ellipse(int(random(100)), int(random(100)), 
         int(random(20)), int(random(20)));  
       sleep(timediff);
     } catch (Exception e) {println("Exception in sleep");}
   }
 } 
}

void setup() {
 stdin = new StringBuffer();
 TimerThread tt1 = new TimerThread(100);
 ColorInput ci = new ColorInput();
 ColorInput ci2 = new ColorInput();
 ci.start(); ci2.start();
 tt1.start(); 
}

void draw() {
}


	  

Reference

Il capitolo 9 del libro Visualizing Data -- Ben Fry; O'Really 2007 fornisce esempi di acquisizione asincrona di dati da file e da siti remoti, con l'utilizzazione di thread e di sincronizzazione.

Content actions

Give Feedback:

E-mail the module author | Rate module ( How does the rating system work?)

Rating system

Ratings

Ratings allow you to judge the quality of modules. If other users have ranked the module then its average rating is displayed below. Ratings are calculated on a scale from one star (Poor) to five stars (Excellent).

How to rate a module

Hover over the star that corresponds to the rating you wish to assign. Click on the star to add your rating. Your rating should be based on the quality of the content. You must have an account and be logged in to rate content.

(0 ratings)

Download:

Add module to:

My Favorites (?)

'My Favorites' is a special kind of lens which you can use to bookmark modules and collections directly in Connexions. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need a Connexions account to use 'My Favorites'.

| A lens (?)

Definition of a lens

Lenses

A lens is a custom view of Connexions content. You can think of it as a fancy kind of list that will let you see Connexions through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to Connexions materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual Connexions member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

| External bookmarks