mag 14

Dart, HTML5 e il frattale di Koch (1/2)

Qualche giorno fa ho cominciato a giocherellare con Dart, questo è il frutto della mia insonnia:

Dart, l’ennesimo tentativo di uccidere javascript.

Dart è un interessante progetto per creare un linguaggio cross-platform e cross-browser funzionante sia lato server che lato client che permetta di sviluppare applicazioni complesse.
L’obiettivo immediato è quindi il sostituire Javascript ( che è un linguaggio oggettivamente impossibile da debuggare ) e in futuro anche i linguaggi server side ( il primo che vorrei eliminare a questo punto è il php) permettendo di utilizzare un solo linguaggio per l’intera applicazione.
Dart dispone sia di una virtual machine ( ancora in alpha ) che un ricompilatore che permette di compilare in javascript in modo da rendere dart avviabile da qualunque browser moderno e ottenendo in futuro, quando la virtual machine si sarà diffusa, prestazioni superiori allo stesso javascript.
Dart è un linguaggio OOP che supporta la programmazione funzionale e può essere usato sia in maniera tipizzata che in maniera non tipizzata (tipizzazione opzionale la chiamano).Supporta l’ereditarietà singola e l’uso delle interfacce, e anche la reflection e la programmazione concorrente.
E’ un progetto molto interessante, non vedo l’ora che entri quantomeno in beta per cominciare a lavorarci seriamente.

La curva di Koch

Per il mio primo “toy program” in dart ho scelto di implementare una classe che disegni la curva di kock in una canvas HTML5.
Non era di geometria che volevo parlare nell’articolo ma descriverò anche matematicamente il modo in cui si può disegnare attraverso il calcolo vettoriale.
La curva di Koch è uno dei primi frattali conosciuti, la sua definizione è più vecchia della definizione formale dei frattali.
La curva si ottiene a partire da un segmento, il segmento viene diviso in 3 parti e nel sottosegmento centrale si ricava un triangolo equilatero, ottenendo quindi una figura formata da 4 segmenti.
La procedura si ripete per ogni segmento, prendo in prestito un immagine da Wikipedia

Il frattale di koch

Animazione della costruzione del frattale di koch

Descriverò la tecnica che ho utilizzato in modo matematico, la trasposizione in codice è abbastanza immediata. Per non perdere di generalità disegnerò una generica porzione del frattale che sia inclinata in modo non banale ( non orizzontale, non verticale e nemmeno a 45°) in modo da evitare che l’esempio ci porti a fare delle semplificazioni che non varrebbero nel caso generico della procedura.

Costruzione di un livello del frattale a partire da un segmento

Costruzione di un livello del frattale a partire da un segmento

1. Si divide il segmento (P1-P5) in 3 parti ottenendo il P2 e P4

Nel caso non ve lo ricordaste la divisione del segmento in un numero arbitrario di parti si ottiene estraendo il vettore che applicato al punto iniziale porta a quello finale, dividendone il modulo per la porzione che si vuole ottenere e applicandolo quindi al punto iniziale del segmento per ottenerne il punto finale.

2. Troviamo P3 applicando al punto medio del segmento ( che si trova come nel passo 1) un vettore lungo quanto l’altezza del triangolo (√(3/4) della lunghezza dei segmenti precedenti) e ruotato di 90° rispetto al vettore di partenza

La rotazione di un vettore si ottiene moltiplicandolo per la matrice di rotazione dell’angolo, nel caso di un angolo di 90° si ha: 

Ogni moltiplicazione per un vettore esprime una rotazione per una fase e una moltiplicazione per un modulo, una matrice di rotazione per un angolo α è la trasposizione in forma matriciale del vettore 1∠ α che equivale alla forma rettangolare cos(α)+ i sin(α) che corrisponde per definizione alla matrice

La procedura che individua i 5 punti della curva partendo da un segmento è quindi:
  List trovaramo(num x1, num y1, num x2, num y2){
    var p2=[x1+(x2-x1)/3,y1+(y2-y1)/3]; // un terzo del segmento
    var p3=[x1+(x2-x1)/2,y1+(y2-y1)/2];//il centro del segmento
    var p4=[x1+(x2-x1)*2/3,y1+(y2-y1)*2/3]; // due terzi del segmento
    var v=[(p2[0]-x1)*r3_4,(p2[1]-y1)*r3_4];
//un vettore allineato al segmento e lungo quanto l'altezza del triangolo
    var vr=[v[1],-v[0]]; //ruoto il vettore di 90 gradi
    p3=[p3[0]+vr[0],p3[1]+vr[1]];
// applico il vettore al punto medio del segmento per ottenere la punta del triangolo
    return [[x1,y1],p2,p3,p4,[x2,y2]];
  }
ripetendo la sequenza nei singoli segmenti si va costruendo il vettore.
Nel prossimo post descriverò l’implementazione in dart del frattale di cui però vi linko il risultato http://www.thedarshan.com/koch/koch.html
Share Button

apr 18

Test

tu non dovresti leggermi

Share Button

feb 13

Implementazione dinamica di una funzione definita ricorsivamente

Implementazione dinamica di una funzione definita ricorsivamente (Appello Aprile 2010)

Sia data la funzione F così definita :

Si richiede di implementare in linguaggio C:

  • Una funzione Fr che calcoli F (n, k);
  • Una funzione Frd che calcoli F (n, k) in O(n2 );

Implementazione ricorsiva

int F(int n, int k)
{
    if(n*k==0)return n+k;
    return F(n,k-1)+ F(n-1,k)+ F(n-1,k-1);
}

La fuzione viene implementata applicando semplicemente la formula di ricorsione

Implementazione iterativa

int Fi(int n, int k)
{
    int **v=malloc(sizeof(int)*(n+1)*(k+1));
    int x=Fi_implementation(n,k,n+1,k+1,v);
    free(v);
    return x;
}

int Fi_implementation(int n, int k, int a, int b,int v[a][b])
{
    int r,c,x;

    for(r=0; r<=n; r++)
    {
        for(c=0; c<=k; c++)
        {
            if(r*c==0)v[r]1=r+c;
            else
                v[r]1=v[r][c-1]+v[r-1]1+v[r-1][c-1];
        }
    }
    x=v[n][k];
    return v[n][k];
}

Il calcolo di ogni elemento n,k richiede il calcolo di tutti gli elementi y<n , n<k e questi elementi vengono usati più di una da tutti gli elementi superiori e a destra, salvandoli in una matrice è possibile ridurre la complessità a O(n*k) occupando O(n*k) memoria.
per facilitare la leggibilità del codice si usa una funzione di appoggio che permette di definire il vettore con le sue dimensioni, altrimenti passando semplicemente un puntatore doppio si dovrebbero calcolare gli indirizzamenti esplicitamente.

Share Button

feb 09

Contare le coppie adiacenti in una sequenza in tempo nLog(n)

Contare le coppie adiacenti in una sequenza in tempo nLog(n) (Appello Settembre 2010)

il testo recita:

Sia data una sequenza di interi a1 , a2 , . . . , an . Diciamo che la sequenza contiene una coppia di numeri consecutivi se esistono due interi ai e aj tali che ai = aj + 1. Implementare un algoritmo per contare il numero di coppie consecutive contenute nella sequenza, che impieghi una complessità di tempo complessiva di n log n e una quantità di memoria aggiuntiva di O(n).

Implementazione attraverso un albero binario

Il problema può essere risolto implementando una treesort e facendo in modo che in fase di inserimento si verifichi l’esistenza delle coppie adiacenti

#define N 10
#include "util.h"
#include "strutture.h"
#include <string.h>  // NULL è definito qui

int piazza(nodo *base, nodo *nodo, int coppie){
    int x=nodo->val-base->val;
    if(x==1 || x==-1){//ho trovato una coppia
        coppie++;
        printf("coppia %d <-> %d (%d) \n",nodo->val,base->val, coppie);
    }
    if(x>0){//maggiore, vado a destra
        if(base->r==NULL){//è vuoto, lo piazzo
            base->r=nodo;
        }else{//non è vuoto, richiamo
            coppie=piazza(base->r, nodo, coppie);
        }
    }else{//è minore, vado a sinistra
        if(base->l==NULL){//è vuoto, lo piazzo
            base->l=nodo;
        }else{//non è vuoto, richiamo
            coppie=piazza(base->l, nodo, coppie);
        }
    }
    return coppie;
}

void trovacoppie(int len, int v[len])
{
    int coppie=0,n;
    nodo *nodi=malloc(sizeof(nodo)*len);

    for(n=0; n<len; n++)
    {
        nodi[n].val=v[n];
        nodi[n].l=NULL;
        nodi[n].r=NULL;
        if(n>0)coppie=piazza(nodi, &nodi[n], coppie);
    }
    free(nodi);
    printf("Un totale di %d coppie trovate\n", coppie);
}

Il contatore delle coppie viene passato a ogni chiamata ricorsiva e viene ritornato in output dalla funzione di ordinamento, in questo modo l’output finale conterrà il conto totale, questo approccio ha complessità O(n*log(n)) , non possono esistere soluzioni con un ordine di complessità minore inquanto non esistono ordinamenti più veloci essendo n*log(n) la dimensione dell’albero generato dalle decisioni necessarie per un ordinamento.
L’albero viene allocato dentro un vettore per facilitarne la deallocazione.

Share Button

feb 06

Determinare la lunghezza della più lunga sottostringa palindroma

Determinare la lunghezza della più lunga sottostringa palindroma ( Appello Aprile 2010)

Il testo recita:

Data una stringa S = a1 a2 . . . an di n caratteri, si propone il problema di determinare la lunghezza della più lunga palindroma sottostringa di S.

Suggerimento:

per la risoluzione: Sia l[i, j] la lunghezza della più lunga palindroma contenuta nella stringa S[i . . . j] = ai ai+1 . . . aj . Il caso base consiste in una stringa di 0 caratteri, cioè il caso in cui i > j. Altrimenti se i caratteri S[i] ed S[j] sono uguali allora posso controllare se la stringa S[i + 1 . . . j − 1] e palindroma ed eventualmente aggiungere i due caratteri S[i] ed S[j]. In tutti gli altri casi devo risolvere ricorsivamente i problemi l[i + 1, j] e l[i, j − 1] e prendere il massimo. La definizione ricorsiva di una soluzione ottima potrebbe essere così definita

La risoluzione del problema mediante l’utilizzo della programmazione dinamica prevede quindi la costruzione di una tabella di dimensione n × n.Si richiede di implementare in linguaggio C:

  1. a) Una funzione lr che calcoli l[1, n] ossia la lunghezza della piùlunga palindroma contenuta nell’intera stringa ;
  2. b) Una funzione lrd che calcoli l[n, k] in O(n2 );

Implementazione Ricorsiva

int m(char s[], int da, int a)
{
    int x;
    if(da>a)return 0;
    if(da==a)return 1;
    if (s[a]==s[da]){
        x=m(s,da+1, a-1);
        if(x==a-da-1)return x+2;
    }
    int r=m(s,da+1,a);
    int l=m(s,da,a-1);
    x=r-l;
    if(x>0)
        return r;
    else
        return l;
}

La fuzione viene implementata applicando semplicemente la formula di ricorsione

Implementazione dinamica

int m_d(char s[], int da, int a,int n,int k, int v[n][k])
{
    int x;
    if(da>a)return 0;
    if(da==a)return 1;
    if (s[a]==s[da]){
        x=v[da+1][a-1];
        if(x==a-da-1)return x+2;
    }
    int r=v[da+1][a];
    int l=v[da][a-1];
    x=r-l;
    if(x>0)
        return r;
    else
        return l;
}

int m_iterativa(char stringa[])
{
    int len=strlen(stringa);
    int **v=malloc(sizeof(int)*len*len);
    int i,j;
    for(i=0;i<len;i++){
        for(j=0;j<(len-i);j++){
            int pal=m_d(stringa,j,i+j,len,len,v);
            v[j*len+(i+j)]=pal;
        }
    }
    len=v[len-1];
    free(v);
    return len;
}

L’implementazione dinamica viene creata modificando la funzione ricorsiva in modo che i valori già calcolati vengano letti da una matrice invece che ricalcolati. I valori per cui c<r (quelli sopra sotto la diagonale principale) sono necessariamente 0 e non vanno calcolati.

L’unico modo di percorrere la matrice senza creare dipendenze da valori non ancora calcolati è per diagonali.

Esempi di matrici dei valori parziali

[
[ 1   1   1   1   3   5   5 ]
[ 0   1   1   1   3   5   5   ]
[ 0   0   1   1   3   3   3   ]
[ 0   0   0   1   1   1   1   ]
[ 0   0   0   0   1   1   1   ]
[ 0   0   0   0   0   1   1   ]
[ 0   0   0   0   0   0   1   ]
]
pabcbaq -> 5
[
[ 1   1   2   4   4   4   4   4   4   4   4   4   4 ]
[ 0   1   2   2   2   2   4   4   4   4   4   4   4   ]
[ 0   0   1   1   1   2   4   4   4   4   4   4   4   ]
[ 0   0   0   1   1   2   4   4   4   4   4   4   4   ]
[ 0   0   0   0   1   2   2   2   3   3   3   3   3   ]
[ 0   0   0   0   0   1   1   1   3   3   3   3   3   ]
[ 0   0   0   0   0   0   1   1   3   3   3   3   3   ]
[ 0   0   0   0   0   0   0   1   1   1   3   3   3   ]
[ 0   0   0   0   0   0   0   0   1   1   3   3   3   ]
[ 0   0   0   0   0   0   0   0   0   1   1   3   3   ]
[ 0   0   0   0   0   0   0   0   0   0   1   1   1   ]
[ 0   0   0   0   0   0   0   0   0   0   0   1   1   ]
[ 0   0   0   0   0   0   0   0   0   0   0   0   1   ]
]
ammaccabanane -> 4
Share Button

feb 04

Ricerca del minimo numero di caratteri da aggiungere a una stringa per renderla palindroma

Ricerca del minimo numero di caratteri da aggiungere a una stringa per renderla palindroma ( Compito del 28 Settembre 2011)

Il testo recita:

Una stringa e palindroma se non cambia leggendola da destra a sinistra e viceversa. Ad esempio anna e osso sono due palindromi.

Risolvere il seguente problema: data una stringa S = {a 1 , a2 , . . . , an } di n caratteri, calcolare il minimo numero di caratteri che occorre inserire per renderla un palindromo.

Suggerimento per la risoluzione:

Sia P [i, j] il numero di caratteri necessari per trasformare la stringa S[i, j] in una stringa palindroma. Il caso base consiste in una stringa di 1 (o meno) caratteri, cioè il caso in cui i = j.

In tal caso ovviamente non serve nessun carattere per rendere la stringa palindroma perch lo gi. Altrimenti se i caratteri S[i] ed S[j] sono uguali allora posso semplicemente risolvere il sottoproblema P [i + 1; j − 1]. Viceversa dovrò inserire almeno un carattere per rendere la stringa palindroma e calcolare il minimo tra i sottoproblemi P [i + 1; j] e P [i; j + 1], risolti ricorsivamente. La definizione ricorsiva di una soluzione ottima potrebbe essere così definita

La risoluzione del problema mediante l’utilizzo della programmazione dinamica prevede quindi la costruzione di una tabella di dimensione n × n.

Implementazione ricorsiva

int p(char s[], int da, int a){
  if(da>=a)return 0;
  if (s[a]==s[da])return p(s, da+1, a-1);
  int r=p(s,da+1,a);
  int l=p(s,da,a-1);
  int x=r-l;
  if(x>0)
    return l+1;
  else
    return r+1;
}

int palindromizza(char stringa[]){
  return p(stringa, 0, strlen(stringa)-1);
}

La fuzione viene implementata applicando semplicemente la formula di ricorsione

Implementazione Dinamica

int p_d(char s[], int da, int a, int r, int c, int v[ r ][ c ] )
{
    if (da >= a)
    {
        return 0;
    }
    if (s[a] == s[da])
    {
        return v[da + 1][a - 1];
    }
    int destra = v[da + 1][a];
    int sinistra = v[da][a - 1];
    int delta = destra - sinistra;
    if (delta > 0)
    {
        return sinistra + 1;
    }
    else
    {
        return destra + 1;
    }
}

int p_iterativa(char stringa[])
{
    int len=strlen(stringa);
    int **v=malloc(sizeof(int)*len*len);
    int i,j;
    for(i=0;i<len;i++){
        for(j=0;j<(len-i);j++){
            int pal=p_d(stringa,j,i+j,len,len,v);
            v[j*len+(i+j)]=pal;
        }
    }
    len=v[len-1];
    free(v);
    return len;
}

L’implementazione dinamica viene creata modificando la funzione ricorsiva in modo che i valori già calcolati vengano letti da una matrice invece che ricalcolati. I valori per cui c>r sono necessariamente 0 (quelli sopra la diagonale principale) e non vanno calcolati.

L’unico modo di percorrere la matrice senza creare dipendenze da valori non ancora calcolati è per diagonali.

Esempi di matrici dei valori parziali

[ 0 0 0 0 0 ]
[ 0 0 0 0 1 ]
[ 0 0 0 1 2 ]
[ 0 0 1 0 1 ]
[ 0 1 2 1 0 ]ABCBA -> 0
[ 0 0 0 0 0 ]
[ 0 0 0 0 1 ]
[ 0 0 0 0 1 ]
[ 0 0 1 1 0 ]
[ 0 1 0 1 1 ]pappa -> 1
[ 0 0 0 0 0 ]
[ 0 0 0 0 1 ]
[ 0 0 0 1 2 ]
[ 0 0 1 2 3 ]
[ 0 1 2 3 4 ]abcde -> 4
Share Button

gen 05

Come creare una sottorete sottoposta a sniffing utilizzando un router supplementare

In questo post descriverò la procedura che ho utilizzato per creare una sottorete all’interno della mia rete domestica da utilizzare per eseguire analisi delle cominicazioni di rete ( per motivi di sviluppo o di sicurezza) o per creare Honeypot. L’idea era quella di creare una seconda rete collegata alla rete domestica e quindi a internet attraverso un host che fa da proxy ( nel senso etimologico del termine) che chiameremo Routing server e logga tutte le conversazioni verso l’esterno per poterle analizzare in seguito.

I requisiti sono quindi:

  • La sottorete creata deve aver accesso diretto e trasparente ad internet
  • Tutto il traffico che attraversa il gateway della sottorete deve essere loggato ( quindi sia quello che esce che quello che entra)
  • Il traffico della sottorrete esistente ( incluso quello generato dal server di routing su eth1 che non è di forwarding) non deve essere loggato
  • Il traffico strettamente interno alla sottorete è irrilevante
  • L’interazione della sottorete con la rete interna preesistente deve essere minimo

La topologia di rete utilizzata è

Descrivendo la topologia dal punto di vista del Routing Server si ha la configurazione seguente:

  • All’interfaccia Eth1 è collegato il router che ha l’accesso ad internet, ad esso è collegata anche la sottorete preesistente
  • L’indirizzo del Routing server in Eth0 è 192.168.1.64
  • L’indirizzo del Routing server in Eth0 è 192.168.0.5, dovrà fare da gateway per il router interno
  • L’indirizzo (interno) del router connesso ad internet è 192.168.1.254
  • L’indirizzo del router interno è 192.168.0.1
  • All’interfaccia Eth0 è collegato un secondo router ( in particolare un router wireless visto che la funzione principale del progetto è studiare applicazioni Android)
  • Il router connesso ad internet ha una sottorrete definita dalla netmask 192.168.1.0/24
  • il router interno una sottorete definita dalla netmask 192.168.0.0/24

Routing Server collega le due sottoreti attraverso funzioni di forwarding implementate attraverso iptables, è quindi un host Linux

Configurazione del Routing server

Come dicevo il routing server è un host linux con due schede di rete che collega le due sottoreti forwardando i pacchetti con iptables. Per rendere possibile questo dobbiamo innanzitutto terminare il servizio network manager ( /etc/init.d/network-manager stop )

La configurazione viene eseguita attraverso uno script e viene quindi annullata al riavvio. lo script è il seguente:

#parte1
pkill dhclient
ifconfig eth0 192.168.0.5
route add -net 192.168.0.0 netmask 255.255.255.0 dev eth0 gw 192.168.1.64
route del default
route add default gw 192.168.1.254

#parte2
echo 1 > /proc/sys/net/ipv4/ip_forward
iptables -F
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -P POSTROUTING ACCEPT
iptables -t nat -P OUTPUT ACCEPT
iptables -t mangle -P PREROUTING ACCEPT
iptables -t mangle -P OUTPUT ACCEPT
iptables -A POSTROUTING -t nat -o eth1 -j MASQUERADE
iptables -A FORWARD -i eth0 -o eth1 -j ACCEPT
iptables -A FORWARD -o eth1 -i eth0 -j ACCEPT

#parte3
iptables -A FORWARD -p tcp -s 192.168.0.5/24 --sport 135 -j DROP #microsoft epmap
iptables -A FORWARD -p udp -s 192.168.0.5/24 --sport 137 -j DROP #netbios ns
iptables -A FORWARD -p tcp -s 192.168.0.5/24 --sport 138 -j DROP #netbios dgm
iptables -A FORWARD -p udp -s 192.168.0.5/24 --sport 1900 -j DROP #SSDP
iptables -A FORWARD -p tcp -s 192.168.0.5/24 --sport 445 -j DROP #microsoft-ds

vediamo di descrivere lo script:

la #parte1

termina i servizi di dhcp ancora in vita e poi setta in maniera statica l’indirizzo di Eth0 e setta il gateway di Eth0 a 192.168.1.64 e il gateway di default a 192.168.1.254 (il router connesso)

la #parte2

attiva e configura il forwarding tra le due interfacce, la prima riga lo attiva nel caso fosse disattivato

la #parte3

filtra i pacchetti provenienti da protocolli tipici di windows e provenienti dalla rete preesistente . questi pacchetti gonfierebbero inutilmente la dimensione dei file di log

Configurazione del router interno

Il router interno dovrà utilizzare 192.168.0.5 come gateway ( indirizzo di Router Server su Eth0) ma utilizzare un dns esterno ( nel mio caso opendns)

L’indirizzamento dhcp deve essere configurato facendo in modo che siano dinamici solo gli indirizzi superiori a 192.168.0.5

Sniffing!

Una volta configurato il tutto lo sniffing è molto semplice, basta avviare uno sniffer qualunque ( tcpdump, wireshark) e metterlo in ascolto su Eth0

In questo modo possiamo anche continuare a navigare senza che il nostro traffico compaia nei log

Share Button

nov 25

le Condition, implementazione delle code di attesa. il problema del barbiere addormentato (C++0x – C++11)

Le Condition

Le Condition sono un meccanismo di sincronizzazione che permette ai thread di sospendersi in attesa che avvenga un evento e di essere risvegliati quando l’evento accade, permettono quindi di creare delle code di attesa.

Nelle STL le condition sono oggetti della classe std::condition_variable , i metodi fondamentali per usarle sono tre:

  • wait() permette di accodarsi alla condition
  • notify_one() notify_all() permettono di risvegliare uno o tutti i thread in attesa sulla coda (nell’implementazione tradizionale dei pthread si chiamavano signal e broadcast)

A una Condition è sempre associato un mutex per evitare race condition che potrebbe creare un thread che si sta preparando a una wait mentre un altro thread esegue una signal, la signal potrebbe infatti avvenire contemporaneamente alla wait e venire quindi persa creando un deadlock, può essere usato un mutex qualunque o si può creare un mutex proprio della condition

Vediamone il funzionamento con un semplice codice: 10 thread si mettono in coda su una condition e un altro thread li sveglia uno per volta

#include <iostream>
#include <thread>
#include <cstdlib>
#include <mutex>
#include <condition_variable>

using namespace std;

class Worker
{
public :

    int numero;
    std::condition_variable *coda;
    mutex *m;
    Worker(int n, std::condition_variable *cond, mutex *mtx)
    {
        coda=cond;
        numero=n;
        m=mtx;
    }
    void operator()()
    {
        cout << "Thread"<< numero << " allocato, mi metto in coda" << endl;
        std::unique_lock<std::mutex> lk(*m);
        coda->wait(lk);
        cout << "Thread"<< numero << " risvegliato dalla coda" << endl;
    }
};

int main()
{
    std::condition_variable *coda= new condition_variable();
    mutex *m= new mutex();
    int a;

    std::thread* threads[10];

    for(a=0;a<10;a++){//creo
        threads[a]= new std::thread(Worker(a+1,coda,m));
        usleep( 100000 );
    }
    for(a=0;a<10;a++){//sveglio
        usleep( 1000 * ( rand() % 900));
        coda->notify_one();
    }
    for(a=0;a<10;a++)//aspetto termine
    {
        threads[a]->join();
        delete(threads[a]);
    }
};

Ma le cose non sono così semplici…

  • Malgrado di solito non succeda le specifiche dei pthread non garantiscono che l’ordine in cui i thread si sveglino sia uguale a quello in cui si addormentano, quindi in caso di applicazioni dove l’ordine degli accessi è importante questo va verificato esplicitamente.
  • Un altra cosa che potrebbe succedere ( e questa è più grave ) è che più di un thread venga liberato dalla coda, quindi i thread appena svegliati devono di nuovo verificare che la condizione che li ha fatti accodare sia stata soddisfatta ed eventualmente rimettersi in coda.

Il problema del barbiere addormentato

Un barbiere di Lisbona...

Un barbiere di Lisbona...

Vediamo un applicazione delle condition a un problema più complesso, il problema del barbiere addormentato di Dijkstra. il problema è definito in questo modo:

Un negozio di barbiere è costituito da una sala con una sola poltrona sulla quale si accomoda il cliente che viene servito e da una sala d’attesa con n sedie. Se non ci sono clienti da servire il bar­biere va a dormire. Se arriva un cliente e tutte le sedie della sala d’attesa sono occupate, il clien­te va via, mentre, se c’è una sedia libera e il barbiere è occupato, il cliente attenderà, seduto, il proprio turno. Se il barbiere sta dormendo (cioè è libero, senza clienti in attesa), il cliente lo sve­glia e viene servito subito.

La soluzione è più prolissa di quello che potrebbe sembrare a prima vista, tralaltro nell’implementarlo ho istintivamente usato un monitor anche se non ho accennato a questo tipo di costrutti, ne do soltanto una breve definizione OOP

Un monitor è un oggetto che incapsula la risorsa ed espone i metodi per accedervi in maniera sicura

In questo caso, come si vede nel sorgente sottostante, il contatore delle sedie occupate è nascosto dentro la classe Stanza che mette a disposizione i metodi per occupare e liberare una sedia mantenendoli sincronizzati attraverso un semaforo proprio della classe.

#include <iostream>
#include <thread>
#include <cstdlib>
#include <mutex>
#include <condition_variable>

using namespace std;

int dormi(float base, float variabile)
{
    int k=1000*1000;
    int base_i=(int)base*k;
    int variabile_i=0;
    if(variabile>0)variabile_i=rand() % (int)(variabile*k);
    usleep(base_i+variabile_i);
    return base_i+variabile_i;
};

class Stanza
{
private:
    int occupate;
    std::mutex monitor_m;

public:

    std::condition_variable attesa;

    std::mutex attesa_m;
    std::condition_variable poltrona;
    std::condition_variable letto;
    std::mutex barbiere;
    int sedie;

    int prenota()
    {
        std::unique_lock<std::mutex> lk(monitor_m);
        if(occupate>=sedie)return -1;
        occupate++;
        cout << occupate << " sedie occupate"<<endl;

        return occupate;
    }

    int guarda()
    {
        std::unique_lock<std::mutex> lk(monitor_m);
        return occupate;
    }

    int libera()
    {
        std::unique_lock<std::mutex> lk(monitor_m);
        occupate--;
        cout << occupate << " sedie occupate"<<endl;
        return occupate;
    }

    Stanza(int n_sedie)
    {
        this->sedie=n_sedie;
        occupate=0;
    }
};

class Barbiere
{
public :

    int numero;
    int azioni;
    Stanza *stanza;
    std::unique_lock<std::mutex> *barbiere;
    Barbiere(Stanza *st)
    {
        azioni=0;
        this->stanza=st;
        barbiere= new std::unique_lock<std::mutex>(stanza->barbiere,std::defer_lock_t());

    }
    void operator()()
    {
        while(true)
        {
            cout << "Barbiere controlla se ci sono clienti in attesa"<<endl;

            if(stanza->guarda()==0)
            {
                cout << "Barbiere si addormenta"<<endl;
                std::unique_lock<std::mutex> lk(stanza->barbiere);
                stanza->letto.wait(lk);
            }

            cout << ++azioni <<" Barbiere serve un cliente"<<endl;
            stanza->attesa.notify_one();
            dormi( 2 , 2 );
            stanza->poltrona.notify_one();
        }
    }
};

class Cliente
{
public :

    int numero;
    Stanza *stanza;
    std::unique_lock<std::mutex> *barbiere;
    Cliente(Stanza *st, int n)
    {
        stanza=st;
        numero=n;
        barbiere= new std::unique_lock<std::mutex>(stanza->barbiere,std::defer_lock_t());

    }
    void work()
    {
        cout << "Thread"<< numero << " allocato, entra dal barbiere" << endl;
        int s=stanza->prenota();

        if(s<0)
        {
            cout << "Thread"<< numero << " tutte le sedie sono occupate, esce" << endl;
            return;
        }
        else
        {
            if(s>0)
            {
                cout << "Thread"<< numero << " si siede e aspetta" << endl;
                if (s==1)
                {
                    cout << "Thread"<< numero << " sveglia il barbiere" << endl;
                    dormi(1,0);
                    stanza->letto.notify_one();
                }
                std::unique_lock<std::mutex> lk(stanza->attesa_m);
                stanza->attesa.wait(lk);
                cout << "Thread"<< numero << " si alza" << endl;
                stanza->libera();
            }

            barbiere->lock();
            cout << "Thread"<< numero << " si siete e si fa rasare" << endl;
            stanza->poltrona.wait(*barbiere);
            barbiere->unlock();
            cout << "Thread"<< numero << " esce" << endl;

        }

    }
    void operator()()
    {
        for(int a=0; a<2; a++)
        {
            work();
            cout << "Thread"<< numero << " va in giro a bighellonare" << endl;
            dormi(30,30);
        }
        cout << "Thread"<< numero << " muore" << endl;
    }
};

int main(int argc, char** argv)
{
    Stanza stanza(5);
    int a;

    std::thread* threads[15];

    std::thread* barbiere=new std::thread(Barbiere(&stanza));
    for(a=0; a<15; a++)   //creo
    {
        dormi(0,a);
        threads[a]= new std::thread(Cliente(&stanza,a+1));
    }
    for(a=0; a<15; a++)   //aspetto termine
    {
        threads[a]->join();
        delete(threads[a]);
    }
};

Il problema è risolto nel modo seguente:

Il barbiere:

  1. Controlla se ci sono clienti in attesa (attraverso i metodi del Monitor)
  2. se non ce ne sono si addormenta ( svegliandosi al punto 3)
  3. se ce ne sono li rade e torna al punto 1

la rasatura consiste in:

  • liberare un Cliente dalla coda della sala di attesa (attesa.notify_one) , il cliente liberato occupa la coda della poltrona
  • radere il Cliente, liberarlo dalla coda della poltrona (poltrona.notify_one())

Il Cliente

  1. Entra nella sala, cerca di sedersi
    1. Se può sedersi:
      1. Se è l’unica persona seduta sveglia il barbiere (letto.notify_one())
      2. va al punto 2
    2. Se non può sedersi esce dalla sala e va al punto 5
  2. Si mette in attesa nella coda della sala (attesa.wait(lk))
  3. una volta svegliato occupa il barbiere (barbiere->lock()) e si mette in attesa sulla sedia della poltrona (poltrona.wait(*barbiere)).
    il mutex sul barbiere evita il problema di una possibile signal non voluta ( vedi sopra il paragrafo “ma le cose non sono così semplici”)
  4. una volta svegliato libera il barbiere ed esce dalla sala
  5. va in giro a bighellonare e poi torna al punto 1

In questa implementazione sono possibili delle signal superflue su un barbiere già sveglio, ma sono irrilevanti

Con questo articolo ho concluso la serie sulla programmazione concorrente in Cpp0X Leggi il resto »

Share Button

nov 09

Uso avanzato dei Mutex, i 5 Filosofi (C++0x – C++11)

Nell’articolo precedente ho parlato del mutex e del suo uso semplice con la lock_guard. in questo tratterò gli altri tipi di guard e l’uso avanzato dei mutex

unique_lock

Con unique_lock un mutex può fare molto di più di quello che permette la lock_guard :

  • Si può cercare di prenotarlo in maniera non bloccante ( se è libero lo si occupa, se non è libero non si attende)
  • Si può cercare di prenotarlo fornendo un timeout massimo di attesa ( se si tratta di un timed mutex ovviamente)
  • Si può accedere direttamente alle funzioni del mutex

e un sacco di altre cose per le quali vi rimando a una reference ( lo so, non è ufficiale… ma non ho trovato di meglio )

Timed mutex e la prenotazione con timeout massimo

Disponendo di un timed mutex potremmo per esempio eseguire questo:

std::timed_mutex m;
std::unique_lock<std::timed_mutex>
 lk(m,std::chrono::milliseconds(5));
if(lk)work()
}

Questa funzione prova ad accedere al mutex aspettando massimo 5 millisecondi, il valore della lock si usa per determinare se l’accesso è avvenuto o no

Accesso alle funzioni interne del mutex

Attraverso la unique_lock possiamo accedere alle funzioni del mutex, ma questa volta le eccezioni sono gestite.

std::mutex m;
void zona_critica()
{
 std::unique_lock<std::mutex> lk(m);
 // in questo momento il mutex è occupato
 lk.unlock();
 // in questo momento il mutex è libero
 lk.lock();
 //in questo momento il mutex è di nuovo occupato
}

Possiamo anche evitare che il mutex venga occupato all’allocazione della unique_lock utilizzando un costruttore diverso:

std::mutex m;
void zona_critica()
{
 std::unique_lock<std::mutex> lk(m,std::defer_lock_t());
 // in questo momento il mutex è libero
 lk.lock();
 // in questo momento il mutex è occupato
 lk.unlock();
 //in questo momento il mutex è di nuovo libero
}

Prenotazione multipla

Un problema molto comune nella programmazione concorrente è la necessita da parte di un processo di accedere in maniera esclusiva a più risorse, è un problema che se gestito male può causare facilmente deadlock o starvation in quanto se un processo che ha acquisito parte delle risorse necessarie attende altre risorse per continuare e queste risorse sono bloccate da altri processi anche loro in attesa tutti i processi restano bloccati in attesa l’uno dell’altro, in questo caso le stl ci vengono in aiuto dandoci un costrutto che permette di bloccare n mutex restando in attesa ma non bloccandoli

void prenota(std::mutex *a ,std::mutex *a)
 {
 std::unique_lock<std::mutex> lock_a(*a, std::defer_lock);
 std::unique_lock<std::mutex> lock_b(*b, std::defer_lock);
 std::lock(lock_a,lock_b);

 // fai qualcosa di critico
 }

Il problema dei 5 filosofi

Illustrazione originale dal testo di Dijkstra

Un problema molto interessante che illustra la potenzialità di questo costrutto è il problema dei 5 filosofi a cena, esso è stato formulato da Dijkstra intorno al 1965, per mettere alla prova le primitive di sincronizzazione dell’epoca ( i semafori fondamentalmente) il problema è questo (cito dal testo di Silberschatz) :

Si considerino cinque filosofi che passano la vita pensando e mangiando. I filosofi con­dividono un tavolo rotondo circondato da cinque sedie, una per ciascun filosofo. Al cen­tro del tavolo si trova una zuppiera colma di riso, e la tavola è apparecchiata con cinque bacchette. Quando un filosofo pensa, non interagisce con i colleghi; quan­do gli viene fame, tenta di prendere le bacchette più vicine: quelle che si trovano tra lui e i commensali alla sua destra e alla sua sinistra. Un filosofo può prendere una bacchetta alla volta e non può prendere una bacchetta che si trova già nelle mani di un suo vicino. Quando un filosofo affamato tiene in mano due bacchette contemporaneamente, man­gia senza lasciare le bacchette. Terminato il pasto, le posa e riprende a pensare.

“Il problema dei cinque filosofi è considerato un classico problema di sincronizzazione, non certo per la sua importanza pratica, e neanche per antipatia verso i filosofi da parte degli informatici, ma perché rappresenta una vasta classe di problemi di controllo della concorrenza, in particolare i problemi caratterizzati dalla necessità di assegnare varie risorse a diversi processi evitando situazioni di stallo e d’attesa indefinita.” la parola starvation deriva proprio da questo problema… se non si riesce a gestire la cosa i filosofi muoiono di fame ( to starve in inglese)

Una semplice soluzione consiste nel rappresentare ogni bacchetta con un mutex: un filosofo tenta di afferrare ciascuna bacchetta eseguendo un’operazione lock su quel mutex e la posa eseguendo una unlock.

Partendo da questo modello possiamo cominciare a cercare una soluzione, quella più immediata potrebbe esere:

un filosofo prende la bacchetta a destra, poi quella a sinistra e poi mangia

ma cosa succede se tutti prendono contemporaneamente la forchetta di destra? un deadlock, muoiono di fame aspettando che qualcuno liberi qualcosa ma nessuno libererà mai niente.

un’altra soluzione potrebbe essere:

Un filosofo di numero pari prende prima la forchetta di destra e poi quella di sinistra, un filosofo dispari il contrario

Ma questo è barare, scartiamola

Ci rendiamo conto che un filosofo deve rilasciare la bacchetta se non può prendere la seconda, qualcosa del tipo

Un filosofo prende la bacchetta di destra, poi cerca di prendere quella di sinistra (trylock) e se non può le posa entrambe e riprova in seguito

ma che succede se i filosofi partono insieme? l’algoritmo va in loop: tutti prendono quella di destra, nessuno può prendere quella a sinistra, tutti posano tutto e si ricomincia, le operazioni necessitano di essere eseguite in maniera atomica:

Un filosofo cerca di prendere entrambe le bacchette contemporaneamente, se non può aspetta

Questo comportamento non può essere costruito direttamente usando i singoli mutex, ma possiamo farlo con il nostro std::lock(lock_a,lock_b)

La soluzione tradizionale necessita della costruzione di un Monitor, ma non me ne occuperò qui, sfruttiamo a pieno le stl e riduciamo il problema a una cosa banale, con buona pace di Dijkstra

...
class Filosofo {
public :

    int id;
    mutex *destra;
    mutex *sinistra;
    Filosofo(int n, mutex *d, mutex *s) {
        id=n;
        destra=d;
        sinistra=s;
    }

    void pensa() {
        string argomenti[5]= {
            "Alla fame nel mondo",
            "Al punto G",
            "All origine dell Universo",
            "Al senso della vita",
            "Alla domanda la cui risposta e' 42"
        };
        cout << id << ": Sto pensando... " << argomenti[id] << endl;
        usleep( 10000 * ( rand() % 90 + 10 ));
    };

    void mangia() {
        printf("%d: Cerco le bacchette...\n",id);
        std::unique_lock<std::mutex> lock_a(*destra, std::defer_lock);
        std::unique_lock<std::mutex> lock_b(*sinistra, std::defer_lock);
        std::lock(lock_a,lock_b);
        printf("%d: Sto mangiando...\n",id);
        usleep( 30000 * ( rand() % 90 + 10 ));
        printf("%d: Poso le bacchette, sazio\n",id);
    };

    void operator()() {
        printf("%d: Penso quindi sono \n",id);
        usleep( 30000 * ( rand() % 90 + 10 ));
        for(int a=0; a<5; a++) {
            pensa();
            mangia();
        }
    }
};
...

La versione completa del codice si trova qui

per questo articolo è meglio fermarmi, sta diventando troppo lungo, le condition le tratto nel prossimo

References

Se siete interessati alla versione originale del problema di Dijkstra ,risolta interamente con i semafori, potete dare un occhiata a questo testo su google docs, se poi avete anche interessi archeologici c’è la scan della versione originale qui (vi avviso, Dijkstra coda in Agol xD)

Che Dio benedica l’università del texas e google docs, visto che la versione ufficiale nel sito della Springer costa 35€

Il testo di Silberschatz che citavo è “Sistemi operativi. Concetti ed esempi” di Abraham Silberschratz, Peter Baer Galvin, Greg Gagne. potete dargli un occhiata qui , è finora il miglior libro tradotto in italiano sui sistemi operativi in cui mi sia imbattuto

L’ambiente utilizzato

Man mano che ci inoltriamo nella parte più raffinata della libreria abbiamo bisogno di versioni sempre più recenti, stavolta ci serve la 4.6 di gcc oltre a tutte le lib descritte nell’articolo iniziale sui thread

Leggi il resto »

Share Button

ott 20

Sincronizzazione dei Thread e gestione delle risorse con i Mutex ( C++0X – C++11)

Come accennato nel post precedente un applicazione che sfrutta il multithreading è costituita da un insieme di processi sequenziali cooperanti, tutti in esecuzione asincrona che possono condividere dati e risorse.

L’accesso concorrente ai dati o alle risorse può provocare incoerenze nei dati o situazioni di deadlock nel caso di risorse che prevedono accesso esclusivo, in questo post cercherò di analizzare gli strumenti che C++0X offre per la sincronizzazione.

All’interno di un processo si possono separare le sezioni in cui lavora su dati interni da quelle in cui si accede a dati o risorse condivise, queste ultime vengono dette sezioni critiche e sono quelle di cui si deve curare la sincronizzazione.

Non tratterò in questo articolo le soluzioni “storiche” e full-software per la gestione delle sezioni critiche (algoritmi di Dekker, di Peterson, algoritmo del banchiere e del panettiere di Lamport, semafori in spinlock) ma tratterò l’uso di funzioni messe a disposizione dalla libreria che ci permettono di assicurare la mutua esclusione tra processi attraverso lo scheduler.

Per prima cosa cerchiamo di definire cosa deve succedere su una sezione critica: una volta individuata e minimizzata una sezione critica dobbiamo fare in modo che

  1. sia verificata la mutua esclusione ossia che soltanto un processo per volta possa trovarsi nella sezione critica
  2. si eviti l’attesa indefinita da parte di un processo che tenta di accedere a una sezione critica occupata
  3. un processo che si trova fuori dalla sezione critica non deve poter interferire con l’accesso alla sezione critica dei processi in attesa

Chi ha lavorato con i pthread in C si renderà conto che è quasi un wrapper a oggetti quello che il cpp ci offre

Il Mutex

Lo strumento fondamentale per la sincronizzazione offerto dalle stl è il Mutex, che è la crasi di mutual exlusion e serve appunto ad evitare che più processi del necessario possano accedere ad una sezione critica.
Esistono 4 tipi di mutex:

  1. semplice ( std::mutex )
  2. semplice con timeout ( std::timed_mutex )
  3. ricorsivo ( std::recursive_mutex )
  4. ricorsivo con timeout ( std::recursive_timed_mutex )
  • un mutex è semplice se il suo stato è binario ( libero/occupato ) è invece ricorsivo se permette più accessi prima di essere occupato
  • un mutex è timed se è possibile fare in modo che si liberi da solo dopo un certo tempo

fondamentalmente un mutex dispone di almeno 3 funzioni:

  • void lock();
    che permette di occuparlo o di attenderlo se è già occupato
  • void unlock();
    che permette di liberarlo
  • bool try_lock();
    che permette occuparlo se è possibile senza però rimanere in attesa

vediamo come usarli, per prima cosa costruiamo un programma che necessiti di essere sincronizzato

#include <iostream>
#include <thread>
#include <cstdlib>
#include <string>

using namespace std;

void stampa(string testo){
 for(int a=0;a<testo.length();a++){
 cout << testo[a];
 flush(cout);
 usleep( 100 * ( rand() % 900 + 100 ));
 }
 cout << endl;
}

class Worker
{
public :

 string testo;
 Worker(string t){
 testo=t;
 }
 void operator()()
 {
 stampa(testo);
 }
};

int main()
{
 thread t1(Worker("Quanto legno roderebbe un roditore se un roditore potesse rodere il legno?"));
 thread t2(Worker("How much wood could a woodchuck chuck if a woodchuck could chuck wood?"));
 t1.join();
 t2.join();
};

il codice ci farà ottenere qualcosa del tipo:

QHowu anmtuoc hl egwonodo  rcooudelrd ebbae  wuoond rocdhiutcokr cehuc ske  uinf a r odiwtooroed chpuoctkes csouled  rchoduecrek i l woloegdno??

La sincronizzazione si effettua creando un mutex globale e condividendolo tra i thread che vanno sincronizzati e racchiudendo la sezione critica dentro una lock e una unlock

...

class Worker
{
public :

 string testo;
 mutex *m;
 Worker(string t, mutex *mtx){
 m=mtx;
 testo=t;
 }
 void operator()()
 {
 m->lock();
 stampa(testo);
 m->unlock();
 }
};

int main()
{
 mutex *m= new mutex();
 thread t1(Worker("Quanto legno roderebbe un roditore se un roditore potesse rodere il legno?",m));
 thread t2(Worker("How much wood could a woodchuck chuck if a woodchuck could chuck wood?",m));
 t1.join();
 t2.join();
};

Mutex e eccezioni

Malgrado l’accesso diretto alle funzioni del mutex sia possibile è un modo pericoloso di procedere.
Infatti non gestisce le eccezioni, se un thread genera un errore all’interno della zona critica il thread viene terminato ma il mutex non viene sbloccato, quindi il modo corretto di gestire una sezione critica sarebbe usare un try{}catch in questo modo:

 void operator()()
 {
    m->lock();
    try
    {
        stampa(testo);
        m->unlock();
    }
    catch(...)
    {
        m->unlock();
        cout << "Yikes!" << endl;
    }
 }

Un modo più compatto di usare in maniera sicura i mutex si può ottenere usando gli ogetti lock_guard delle stl, che permettono anche di rilasciare il mutex in maniera automatica quando si esce dal blocco in cui sono definite ( lo sbloccano nel loro distruttore per essere precisi), il codice precedente si può riscrivere in questo modo:

 void operator()()
 {
    lock_guard<mutex> lock(*m);
    stampa(testo);
 }

Precisiamo una cosa però…

Il fatto che la guard blocchi il mutex non implica che l’intero programma non venga terminato… infatti dopo aver sbloccato il mutex propaga l’eccezione generata a livello superiore e se non c’è un catch a livello superiore questo equivale alla terminazione del processo padre

Quindi se ci limitiamo a guardare questi snippet l’esempio con la gestione manuale funziona meglio di quello con la lock ma nel caso generale l’uso della lock è comodo perché ci permette di scrivere codici per la gestione delle eccezioni che non tengono conto del mutex…spero di essermi spiegato

E con questo concludo l’articolo, nel prossimo tratterò gli altri tipi di guard e le condition usati per la gestione di situazioni più complesse

Leggi il resto »

Share Button

Post precedenti «

» Post successivi