esempio thread in python: calcolo della matrice prodotto

Vediamo ora un esempio di programma per il calcolo di una matrice prodotto in python che esegue il calcolo di ogni elemento in un thred differente

Matematicamente l’elemento C{}under{i,j} della matrice prodotto è definito da

C{}under{i,j}=sum{n=1}{K}{A{}under{i,n}B{}under{n,j}}

Il programma andrà quindi a definire una lista di thread, aspetterà il completamento di tutti e scriverà a avideo la matrice prodotto.
Inserisco per prima cosa le tre matrici coinvolte: A e B sono le matrici che verranno moltiplicate e C sarà usato per contenere il risultato.
Il programma è in grado di gestire anche matrici di dimensioni differenti anche se A e B qui hanno una dimensione specifica. Si può quindi rieseguire l’esempio semplicemente sostituendo A e B o modificandolo per andare a leggere da disco i valori.

if __name__ == "__main__":
   """ inizializzo le tre matrici"""
   # definisco le matrici iniziali
   A = [ [1,1,1], [1,1,1]]
   B = [ [1,1], [1,1],[1,1]]
   C = [[0 for j in range(len(A))] for i in range(len(B[0]))]

Definisco una lista in cui raggrupperò i thread che in questo caso utilizzerò per assicurarmi che tutti i thread hanno completato la loro attività prima di stampare il risultato.
Il codice contiene anche, commentata, la definizione di un oggetto lock che può essere utilizzato per controllare la sincronizzazione dei thread. In questo caso però non è utile dato che le operazioni eseguite dai vari thread sono indipendenti.

   
   # acquisizione del lock
   # threadLock = threading.Lock()
   threads = []

Definisco e avvio poi i vari thread sfruttando un’apposita classe myThread che descriveremo più sotto nel dettaglio.

   # avvio in thread per ogni elemento della nuova matrice
   thread_id = 0
   for row in range( len(A) ):
      for column in range( len(B[0]) ):  
         # Create new threads
         threads_a = myThread(thread_id, "Thread-"+str(row)+"_"+str(column), row, column)
         # Start new Threads
         threads_a.start()
         # Add threads to thread list
         threads.append(threads_a)
         thread_id = thread_id + 1

attendo il completamento di tutti i thread

   #    Wait for all threads to complete
   for t in threads:
      t.join()

stampo in fine il risultato

   for i,v in enumerate(C):
      print(C[i])

Passiamo ora alla classe myThread che è costituita ridefinendo la classe threading.Thread.
In particolare ridefiniamo due funzioni: un costruttire e la funzione run che viene eseguita dal thread avviato con lo start().
Il costruttore si limita a definire nell’oggetto alcune variabili passate per argomento

class myThread( threading.Thread ):
   def __init__(self, threadID, name, row, column):
      threading.Thread.__init__(self)
      self.threadID = threadID
      self.name = name
      self.row = row
      self.column = column

la funzione run invece contiene tutto il calcolo dell’elemento di matrice

   def run( self ):
      print ( "Starting " + self.name )
      # Get lock to synchronize threads
      # threadLock.acquire()
      # calculate matrix element
      self.totale = 0
      for a in range(len(A[self.row])):
         for b in range( len(B) ):
            if( a == b ): 
               self.totale = self.totale + A[self.row][a] * B[b][self.column]
            #print("{:s}, self.totale = {:d}".format(self.name, self.totale) )

      C[self.row][self.column] = self.totale
      # Free lock to release next thread
      # threadLock.release()

Decommentando la riga print si può vedere come i thread procedono in parallelo.

Riporto sotto il codice completo

class myThread( threading.Thread ):
def __init__(self, threadID, name, row, column):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.row = row
self.column = column
def run( self ):
print ( "Starting " + self.name )
# Get lock to synchronize threads
# threadLock.acquire()
# calculate matrix element
self.totale = 0
for a in range(len(A[self.row])):
for b in range( len(B) ):
if( a == b ):
self.totale = self.totale + A[self.row][a] * B[b][self.column]
#print("{:s}, self.totale = {:d}".format(self.name, self.totale) )

C[self.row][self.column] = self.totale
# Free lock to release next thread
# threadLock.release()

if __name__ == "__main__":
""" inizializzo le tre matrici"""
# definisco le matrici iniziali
A = [ [1,1,1], [1,1,1]]
B = [ [1,1], [1,1],[1,1]]
C = [[0 for j in range(len(A))] for i in range(len(B[0]))]

# acquisizione del lock
# threadLock = threading.Lock()
threads = []

# avvio in thread per ogni elemento della nuova matrice
#numThread = A.len() * B[0].len()
thread_id = 0
for row in range( len(A) ):
for column in range( len(B[0]) ):
# Create new threads
threads_a = myThread(thread_id, "Thread-"+str(row)+"_"+str(column), row, column)
# Start new Threads
threads_a.start()
# Add threads to thread list
threads.append(threads_a)
thread_id = thread_id + 1

# Wait for all threads to complete
for t in threads:
t.join()

for i,v in enumerate(C):
print(C[i])


			

esempio libreria pthread in c

Vediamo ora un esempio di utilizzo della libreria pthread in C. Scriviamo un programma che troverà tutti i numeri primi minori ed uguali ad un numero passato a linea di comando.

Per prima cosa in C bisogna includere le librerie che si utilizzeranno.

#include <pthread.h>
#include  <stdio.h>
#include  <stdlib.h>

importo quindi la libreria pthread per la gestione dei thread, la libreria stdio per comunicare i risultati e la stdlib per gestire l’input.

Dichiaro poi le variabili che utilizzero’ per far comunicare il processo padre e i suoi thred e le funzioni che verranno esplicitate più sotto.

long int value = 0, upper_limit;
int isPrime( int number );
void *runner( void *param );

qui particolarmente rilevante è *runner(): definendo il thread dirò che la sua esecuzione inizierà da questa funzione.

Segue poi il corpo principale del programma e le definizioni delle variabili locali alla funzione

int main( int argc, char * argv[] )
{
        int pid;
        pthread_t tid;
        pthread_attr_t attr;
        char *endptr;

qui la variabile tid verrà usata per contenere l’identificativo del thread, mentre attr è una struttura con gli attributi del thread relativi ad esempio allo scheduling. Qui non li specifichiamo ed utilizzeremo i valori di default.

Recupero il parametro in ingresso controllando l’esistenza del parametro stesso

        if ( argc < 2 ) {
                printf( "Usage: %s ", argv[1] );
        } else upper_limit =  strtol(argv[1], &endptr, 10 );

        printf("PARENT: upper_limit = %d\n", (int)upper_limit );

Passo poi alla generazione del thread vero e proprio:
Per prima cosa recupero i valori di default per attr

        pthread_attr_init( &attr );

Creo poi il thread recuperando il tid, gli attr e specificando che l’esecuzione del thread deve essere nella funzione runner.

        pthread_create( &tid, &attr, runner, NULL );

devo poi solo attendere la conclusione delle attività del thread.

        
        pthread_join( tid, NULL );

Concludo poi l’esecuzione.

        printf( "End of Child\n" );
}

Seguono poi le due funzioni che sono utilizzate in questo programma. La prima è quella che verifica se un numero è primo. Non è particolarmente efficace ma è chiara e semplice e assolutamente adeguata ad un esempio di uso dei thread.

int isPrime(int number)
{
    int i;
    for ( i = 2; i < number; i++)
    {
      if (number % i == 0 && i != number)
        return 0;
    }
    return 1;
}

Semplicemente, per il numero che viene passato, verifico tutti i possibili divisori.

Segue poi la funzione richiamata con il thread:

void *runner( void *param ) {
        int prime;
        int lista[upper_limit];
        int i,j;
        printf( "START runner: %d\n", (int)upper_limit );

        printf("1\n");
        for( i = 2; i <= upper_limit; i++ ){
                if( isPrime( i ) ) printf("%d\n", i);
        }
        pthread_exit(0);
}

funzione che scriverà a video i numeri individuati come primi dalla funzione precedente.

Riporto sotto il listato dell’intero programma. Per compilarlo con gcc bisogna specificare il parametro -pthread.

gcc -pthread prime_thread.c


#include <pthread.h>
#include  <stdio.h>
#include  <stdlib.h>

long int value = 0, upper_limit;
int isPrime( int number );
void *runner( void *param );

int main( int argc, char * argv[] )
{
        int pid;
        pthread_t tid;
        pthread_attr_t attr;
        char *endptr;

        if ( argc < 2 ) {
                printf( "Usage: %s ", argv[1] );
        } else upper_limit =  strtol(argv[1], &endptr, 10 );

        printf("PARENT: upper_limit = %d\n", (int)upper_limit );

        pthread_attr_init( &attr );
        pthread_create( &tid, &attr, runner, NULL ); 
        pthread_join( tid, NULL ); 
        printf( "End of Child\n" );
}

int isPrime(int number)
{
    int i;
    for ( i = 2; i < number; i++)
    {
      if (number % i == 0 && i != number)
        return 0;
    }
    return 1;
}

void *runner( void *param ) {
        int prime;
        int lista[upper_limit];
        int i,j;

        printf( "START runner: %d\n", (int)upper_limit );

        printf("1\n");
        for( i = 2; i <= upper_limit; i++ ){
                if( isPrime( i ) ) printf("%d\n", i);
        }
        pthread_exit(0);
}

python – fork e comunicazione tra processi

Nel seguente programma, utilizzando il calcolo della serie di fibonacci per pretesto, mostro un esempio di utilizzo del fork e della comunicazione tra processi.

Ai fini del calcolo dei termini della successione di fibonacci utilizzo una funzione ricorsiva che richiama se stessa fino a che non arriva a termini noti: non è il massimo per quel che riguarda le prestazioni ma è comoda e compatta e in questo caso è solo un pretesto.

Iniziamo dal listato del programma:

#!/usr/bin/env python3 
import os, sys

def fib_rec(n):
    """calculate Fibonacci series up to n"""
    if n == 1:
       return 1
    elif n == 2:
       return 1
    else:
       return fib_rec(n-1)+fib_rec(n-2)

while 1:
    r,w=os.pipe()

    num = int(input("Please enter an integer: "))

    pid = os.fork()
    if pid:          # Parent
        while 1:
            data=os.read(r,64)
            if data == (" ").encode():
                break
            else:
                print (str(pid)+": child calculate: " + str(data))
    else:           # Child
        count=1;
        while count <= num:
            fibn =  fib_rec(count)
            # il loop serve a passare parole di 64 byte fisse visto che poi leggo byte per byte
            count2 = 1
            while count2 <= 64 - len(str(fibn)):
                os.write(w, ("").encode());
                count2 = count2 + 1
            msg = (str(fibn)).encode()
            os.write(w, msg)
            count = count+1
        # invio stringa vuota per chiudere pipe
        count2 = 1
        while count2 < 64 :
            os.write(w, ("").encode());
            count2 = count2 + 1
        os.write(w, (" ").encode());
        exit(0)

Non spieghiamo nel dettaglio fib_rec che è semplicemente una funzione ricorsiva per il calcolo della successione di fibonacci.

Il programma è costituito da un loop infinito nel quale viene chiesto quanto deve essere lunga la successione da calcolare. Ad ogni ciclo il programma definisce una pipe, genera un processo child per eseguire il calcolo e mostra a video i risultati calcolati dal processo figlio e passati al processo padre.

Vediamo più nel dettaglio:

    r,w=os.pipe()

definisce la pipe e restituisce due file descriptor; il primo è utilizzabile per leggere ed il secondo per scrivere.

Una volta ottenuta la lunghezza della sequenza da calcolare, il programma genera il figlio

pid = os.fork()

l'esecuzione del programma procede da qui sia per il processo padre sia per quello figlio ma nel caso del padre pid conterrà il pid del figlio, nel caso del figlio pid conterrà il valore 0. Questo viene usato per distinguere il comportamento di padre e figlio.

Il processo padre entra in un loop infinito in cui legge parole di 64 byte dalla pipe e da cui esce se il valore ritornato è uno spazio.

    if pid:          # Parent
        while 1:
            data=os.read(r,64)
            if data == (" ").encode():
                break
            else:
                print (str(pid)+": child calculate: " + str(data))

Il processo figlio, di contro, calcola i termini della successione di fibonacci e li scrive nella pipe assieme a tanti caratteri vuoti quanti ne servono per completare la parola di 64 byte. Terminata la sequenza, invia una stringa contenente un solo spazio per dire al processo padre di concludere il suo ciclo ed esce.

        count=1;
        while count <= num:
            fibn =  fib_rec(count)
            # il loop serve a passare parole di 64 byte fisse visto che poi leggo byte per byte
            count2 = 1
            while count2 <= 64 - len(str(fibn)):
                os.write(w, ("").encode());
                count2 = count2 + 1
            msg = (str(fibn)).encode()
            os.write(w, msg)
            count = count+1
        # invio stringa vuota per chiudere pipe
        count2 = 1
        while count2 < 64 :
            os.write(w, ("").encode());
            count2 = count2 + 1
        os.write(w, (" ").encode());
        exit(0)

backup e rsync

Avere una politica di backup che ci permetta di limitare i danni in caso di errori o problemi è una necessità che risale all’origine dell’informatica.

UNIX e Linux hanno ovviamente diversi tool che vengono in aiuto per questa necessità, e, almeno nel caso di una macchina singola, si possono implementare delle politiche asolutamente soddisfacenti con soli tool interni come cron ed rsync.

Come molti comandi della shell UNIX rsync è estremamente versatile. Il comando di base è:

rsync /path/to/source/file /path/to/destination

Come suggerisce il nome rsync non è un semplice tool per la copia dei file ma si occupa di sincronizzare due directory effettuando la copia dei soli file che lo richiedono.

Tendenzialmente rsync è utile soprattutto se si ha la necessità di copiare molti file e quindi sarà necessario utilizzare l’opzione -r

rsync -r /path/to/source/ /path/to/destination
rsync -r /path/to/source /path/to/destination

Ho riportato due comandi per sottolineare una particolarità di rsync. Se si copia una directory e si specifica / nel path della sorgente il contenuto della directory verrà copiato nella destinazione; se invece il path viene scritto senza / è la directory che viene copiata nella destinazione.

Rsync generalmente non ha quasi nessun output a video, il che è ottimo per script in crontab, se però si sta eseguendo il backup in real time si vorrà probabilmente un’indicazione di come sta procedendo l’attività. PEr questo si hanno due opzioni -v e –progress.

rsync generalmente controlla le dimensioni e la data di ultima modifica per capire se devono essere copiate. Sono però disponibili specifiche opzioni per fare controlli differenti. Ad esempio –size-only se si vole considerare solo le dimensioni del file o –checksum se si vuole un conformto più affidabile anche se più oneroso.

Le opzioni di questo comando sono svarite ma generalmente se si stanno effettuando dei backup -a farà si che vengano attivate le opzioni che più comunemente vengono utilizzate per questa attività.

Una mensione particolare merita l’opzione -b; in questa modalità rsync effettuerà un backup incrementale spostando i file rimossi dalla sorgente in un appositp path e scrivendo su un file tutte le operazioni effettuate. Si otterrà quindi una directory allineata a quella sorgente e si manterranno i file e le informazioni necessari per ricostruire la situazione al momento del backup precedente.

Un esempio di comando reale potrebbe quindi essere

rsync -avb --size-only --delete --log-file=/path/to/file_log/file.log --backup-dir=/path/to/directory_file_incrementali/ --progress /directory/source/ /directory/destination/

python e automi cellulari – life

Volendo farmi un’idea del python, dopo aver letto il tutorial ho pensato di provare a fare un piccolo programma e, avendo recentemente letto un articolo sugli automi cellulari la scelta è caduta su un’implementazione del famoso gioco life.

Per chi non lo conoscesse, life è un automa cellulare, ovvero un sistema che evolve sulla base di regole di interazione tra gli elementi del sistema. Nel caso specifico di life il sistema è costituito da una tabella in cui ogni elemento è costituito da una casella che può essere in due stati: morto o vivo. Ogni casella ha 8 vicini:

  1. Se una casella è morta ed ha esattamente 3 vicini vivi, diventa viva. Altrimenti rimane morta
  2. Se una casella è viva ed ha 2 o 3 vicini vivi rimane viva altrimenti muore

nell’implementazione che ho realizzato il sistema è a topologia toroidale:

  1. l’elemento a destra di quello a più a destra è l’elemento più a sinistra della stessa riga,
  2. l’elemento a sinistra di quello più a sinistra è l’elemento più a destra della stessa riga
  3. l’analogo per le colonne

Riporto ora il programma da copiare in un file ed eseguire con python 3.
In questa implementazione ho trovato comodo dare valore 0 alle cellule more ed 1 a quelle vive.

import random

def evolve( array ):
### esegue lo step di evoluzione del gioco life su una tabella sferica
   rows = len(array)
   columns = len(array[0])
   array2 = [[0 for j in range(rows)] for i in range(columns)]

# i=0, j=0 
   totale = array[rows-1][columns-1]+array[rows-1][0]+array[rows-1][1]+array[0][columns-1]+array[0][0]+array[0][1]+array[1][columns-1]+array[1][0]+array[1][1]
   if    array[0][0] == 0:
      if totale == 3:
         array2[0][0]=1
      else:
         array2[0][0]=0
   if array[0][0] == 1:
      if totale <= 2:
         array2[0][0]=0
      elif totale <= 4:
         array2[0][0]=1
      else:
         array2[0][0]=0
# i=rows-1, j=0 
   totale = array[rows-2][columns-1]+array[rows-2][0]+array[rows-2][1]+array[rows-1][columns-1]+array[rows-1][0]+array[rows-1][1]+array[0][columns-1]+array[0][0]+array[0][1]
   if    array[rows-1][0] == 0:
      if totale == 3:
         array2[rows-1][0]=1
      else:
         array2[rows-1][0]=0
   if array[rows-1][0] == 1:
      if totale <= 2:
         array2[rows-1][0]=0
      elif totale <= 4:
         array2[rows-1][0]=1
      else:
         array2[rows-1][0]=0

# i=0, j=columns-1
   totale = array[rows-1][columns-2]+array[rows-1][columns-1]+array[rows-1][0]+array[0][columns-2]+array[0][columns-1]+array[0][0]+array[1][columns-2]+array[1][columns-1]+array[1][0]
   if    array[0][columns-1] == 0:
      if totale == 3:
         array2[0][columns-1]=1
      else:
         array2[0][columns-1]=0
   if array[0][columns-1] == 1:
      if totale <= 2:
         array2[0][columns-1]=0
      elif totale <= 4:
         array2[0][columns-1]=1
      else:
         array2[0][columns-1]=0

# i=rows-1, j=columns-1 
   totale = array[rows-2][columns-2]+array[rows-2][columns-1]+array[rows-2][0]+array[rows-1][columns-2]+array[rows-1][columns-1]+array[rows-1][0]+array[0][columns-2]+array[0][columns-1]+array[0][0]
   if    array[rows-1][columns-1] == 0:
      if totale == 3:
         array2[rows-1][columns-1]=1
      else:
         array2[rows-1][columns-1]=0
   if array[rows-1][columns-1] == 1:
      if totale <= 2:
         array2[rows-1][columns-1]=0
      elif totale <= 4:
         array2[rows-1][columns-1]=1
      else:
         array2[rows-1][columns-1]=0

# i=0
   for j in range( 1, columns-2 ):
      totale = array[rows-1][j-1]+array[rows-1][j]+array[rows-1][j+1]+array[0][j-1]+array[0][j]+array[0][j+1]+array[1][j-1]+array[1][j]+array[1][j+1]
      #print( totale )
      if    array[0][j] == 0:
         if totale == 3:
            array2[0][j]=1
         else:
            array2[0][j]=0
      if array[0][j] == 1:
         if totale <= 2:
            array2[0][j]=0
         elif totale <= 4:
            array2[0][j]=1
         else:
            array2[0][j]=0

# i=rows-1
   for j in range( 1, columns-2 ):
      totale = array[rows-2][j-1]+array[rows-2][j]+array[rows-2][j+1]+array[rows-1][j-1]+array[rows-1][j]+array[rows-1][j+1]+array[0][j-1]+array[0][j]+array[0][j+1]
      #print( totale )
      if    array[rows-1][j] == 0:
         if totale == 3:
            array2[rows-1][j]=1
         else:
            array2[rows-1][j]=0
      if array[rows-1][j] == 1:
         if totale <= 2:
            array2[rows-1][j]=0
         elif totale <= 4:
            array2[rows-1][j]=1
         else:
            array2[rows-1][j]=0

# j=0
   for i in range( 1, rows-2 ):
      totale = array[i-1][columns-1]+array[i-1][0]+array[i-1][1]+array[i][columns-1]+array[i][0]+array[i][1]+array[i+1][columns-1]+array[i+1][0]+array[i+1][1]
      #print( totale )
      if    array[i][0] == 0:
         if totale == 3:
            array2[i][0]=1
         else:
            array2[i][0]=0
      if array[i][0] == 1:
         if totale <= 2:
            array2[i][0]=0
         elif totale <= 4:
            array2[i][0]=1
         else:
            array2[i][0]=0

# j=columns-1
   for i in range( 1, rows-2 ):
      totale = array[i-1][columns-2]+array[i-1][columns-1]+array[i-1][0]+array[i][columns-2]+array[i][columns-1]+array[i][0]+array[i+1][columns-2]+array[i+1][columns-1]+array[i+1][0]
      #print( totale )
      if    array[i][columns-2] == 0:
         if totale == 3:
            array2[i][columns-2]=1
         else:
            array2[i][columns-2]=0
      if array[i][columns-2] == 1:
         if totale <= 2:
            array2[i][columns-2]=0
         elif totale <= 4:
            array2[i][columns-2]=1
         else:
            array2[i][columns-2]=0

# other cases
   for i in range( 1, rows-2 ):
      for j in range( 1, columns-2 ):
         totale = array[i-1][j-1]+array[i-1][j]+array[i-1][j+1]+array[i][j-1]+array[i][j]+array[i][j+1]+array[i+1][j-1]+array[i+1][j]+array[i+1][j+1]
         #print( totale )
         if    array[i][j] == 0:
            if totale == 3:
               array2[i][j]=1
            else:
               array2[i][j]=0
         if array[i][j] == 1:
            if totale <= 2:
               array2[i][j]=0
            elif totale <= 4:
               array2[i][j]=1
            else:
               array2[i][j]=0

   return array2

# j=0
   for i in range( 1, rows-2 ):
      totale = array[i-1][columns-1]+array[i-1][0]+array[i-1][1]+array[i][columns-1]+array[i][0]+array[i][1]+array[i+1][columns-1]+array[i+1][0]+array[i+1][1]
      #print( totale )
      if    array[i][0] == 0:
         if totale == 3:
            array2[i][0]=1
         else:
            array2[i][0]=0
      if array[i][0] == 1:
         if totale <= 2:
            array2[i][0]=0
         elif totale <= 4:
            array2[i][0]=1
         else:
            array2[i][0]=0

# j=columns-1
   for i in range( 1, rows-2 ):
      totale = array[i-1][columns-2]+array[i-1][columns-1]+array[i-1][0]+array[i][columns-2]+array[i][columns-1]+array[i][0]+array[i+1][columns-2]+array[i+1][columns-1]+array[i+1][0]
      #print( totale )
      if    array[i][columns-2] == 0:
         if totale == 3:
            array2[i][columns-2]=1
         else:
            array2[i][columns-2]=0
      if array[i][columns-2] == 1:
         if totale <= 2:
            array2[i][columns-2]=0
         elif totale <= 4:
            array2[i][columns-2]=1
         else:
            array2[i][columns-2]=0

# other cases
   for i in range( 1, rows-2 ):
      for j in range( 1, columns-2 ):
         totale = array[i-1][j-1]+array[i-1][j]+array[i-1][j+1]+array[i][j-1]+array[i][j]+array[i][j+1]+array[i+1][j-1]+array[i+1][j]+array[i+1][j+1]
         #print( totale )
         if    array[i][j] == 0:
            if totale == 3:
               array2[i][j]=1
            else:
               array2[i][j]=0
         if array[i][j] == 1:
            if totale <= 2:
               array2[i][j]=0
            elif totale <= 4:                
               array2[i][j]=1
            else:
               array2[i][j]=0
    return array2 

def start_configuration( lenght = 6 ): 
### genera la tabella iniziale quadrata e di dimensione iniziale lenght
    board = [[]]
    line = []
    random.seed()

    for a in range( lenght ):
       line = []
       for b in range( lenght ):
           tmp = random.randint( -1, 1 )
           if tmp > 0:
             line.append( 1 )
          else:
             line.append( 0 )
      board.append( line )

   board.remove([])
   return board

board = [[]]
board = start_configuration( 20 )

for i,v in enumerate(board):
   print(board[i])

print("\n")

while True:
   ok = input()
   board = evolve( board )
   for i,v in enumerate(board):
      print(board[i])

Dopo aver importato il modulo random che utilizzerò per generare la configurazione iniziale, il programma definisce due funzioni: la prima per calcolare uno step di evoluzione, la seconda per generare una configurazione iniziale. Segue poi il corpo principale.

Iniziamo ad esaminare il programma a partire dal corpo principale che è piuttosto semplice:

board = [[]]
board = start_configuration( 20 )

inizio dichiarando la tabella come lista di liste e gli assegno una configurazione con una funzione che esamineremo in seguito. Alla funzione start_configuration() passo come parametro il numero di righe e colonne: ho presupposto che fosse quadrata.

for i,v in enumerate(board):
   print(board[i])

stampo a video la configurazione iniziale con un semplice loop sugli elementi della lista. enumerate restituisce come primo valore l’indice numerico dell’elemento di una lista, indice che poi uso per stampare l’elemento.

while True:
   ok = input()
   board = evolve( board )
   for i,v in enumerate(board):
      print(board[i])

entro infine in un loop infinito per il calcolo degli stati successivi del sistema. Per avere controllo sul processo ad ogni ciclo attendo un invio da parte dell’utente e poi calcolo e stampo a video lo stato successivo.

Esaminiamo ora la prima funzione.

import random

start_configuration() genera casualmente la configurazione iniziale e per farlo ho importato il modulo random

def start_configuration( length = 6 ):
### genera la tabella iniziale quadrata e di dimensione iniziale length

segue poi la definizione della funzione nella quale ho impostato un valore di default per la variabile passata e una riga di commento.
Questa riga di commento ad inizio funzione e preceduta da tre # è in qualche modo speciale e serve a documentare il codice. Appositi tool possono essere utilizzati per esaminare il codice in python alla loro ricerca e generare automaticamente della documentazione.

   board = [[]]
   line = []

definisco le liste che mi serviranno

   random.seed()

inizializzo il generatore di numeri casuali.

   for a in range( length ):
      line = []
      for b in range( length ):
          tmp = random.randint( -1, 1 )
          if tmp > 0:
             line.append( 1 )
          else:
             line.append( 0 )
      board.append( line )

entro nel loop che genera la tabella casuale. Dato che la tabella è in realtà una lista di liste la cosa che mi è sembrata funzionare meglio è stata genereare una riga alla volta per poi aggiungerla alla tabella utilizzando la funzione append delle liste.

   board.remove([])

per come l’ho generata, la tabella conteneva una riga vuota che quindi rimuovo.

   return board

Restituisco infine la tabella generata.

La funzione evolve() sebbene molto lunga non è particolarmente complicata. La sua lunghezza deriva dalla casistica articolata necessaria per gestire cosa accade sui bordi della tabella.

def evolve( array ):
### esegue lo step di evoluzione del gioco life su una tabella sferica
   rows = len(array)
   columns = len(array[0])
   array2 = [[0 for j in range(rows)] for i in range(columns)]

[...]
# other cases
   for i in range( 1, rows-2 ):
      for j in range( 1, columns-2 ):
         totale = array[i-1][j-1]+array[i-1][j]+array[i-1][j+1]+array[i][j-1]+array[i][j]+array[i][j+1]+array[i+1][j-1]+array[i+1][j]+array[i+1][j+1]
         #print( totale )
         if    array[i][j] == 0:
            if totale == 3:
               array2[i][j]=1
            else:
               array2[i][j]=0
         if array[i][j] == 1:
            if totale <= 2:
               array2[i][j]=0
            elif totale <= 4:
               array2[i][j]=1
            else:
               array2[i][j]=0

   return array2

ho riportato quindi solo il caso generico.

Dato che lo stato nel tempo successivo dipende dal numero di vicini e dato che si è scelto di assegnare 1 per il caso cella viva e 0 per quello cella morta, tutto quello che serve è sommare i valori delle caselle coinvolte e confrontare il risultato con le regole che definiscono il gioco.

Il programma andrebbe migliorato sotto diversi aspetti. In particolare:

  • trovare un modo più elegante per gestire la variegata casistica dell’evoluzione per le caselle lungo il bordo.
  • stampare lo stato in modo più visibile
  • dare al  programma un aspetto più unix like, permettendo di eseguirlo passando dei parametri, come le dimensioni delle tabelle, un fiel con una configurazione iniziale, se si vuole che il programma aspetti l’ok dell’esecutore per passare allo stato successivo o eseguire un numero predeterminato di step, altro.
  • capire se il python ha strutture dati più adatte
  • molto altro probabilmente.

PS 1

Si può usare l’operatore modulo per scrivere in modo più compatta la funzione evolve() che diverrà

def evolve( array ):
### esegue lo step di evoluzione del gioco life su una tabella sferica
   rows = len(array)
   columns = len(array[0])
   array2 = [[0 for j in range(rows)] for i in range(columns)]

   for i in range( 0, rows ):
      for j in range( 0, columns ):
         totale = array[(i-1)%(rows)][(j-1)%(columns)]+array[(i-1)%(rows)][j%(columns)]+array[(i-1)%(rows)][(j+1)%(columns)]+array[i%(rows)][(j-1)%(columns)]+array[i%(rows)][j%(columns)]+array[i%(rows)][(j+1)%(columns)]+array[(i+1)%(rows)][(j-1)%(columns)]+array[(i+1)%(rows)][j%(columns)]+array[(i+1)%(rows)][(j+1)%(columns)]
         #print( totale )
         if array[i][j] == 0:
            if totale == 3:
               array2[i][j]=1
            else:
               array2[i][j]=0
         if array[i][j] == 1:
            if totale <= 2:
               array2[i][j]=0
            elif totale <= 4:
               array2[i][j]=1
            else:
               array2[i][j]=0

   return array2

command line perl e analisi dei log

Tempo fa abbiamo visto come utilizzare il perl come tool di linea di comando. Vediamo oggi come utilizzarlo in un caso un poco più complesso: per contare le occorrenze di un dato vhost in un access.log di apache e magari al tempo stesso avere le statistiche di quante volte apache da una risposta corretta (return code 200) o qualche altro errore.

Osserviamo per prima cosa una riga di log del nostro sito preferito

xx.xx.xx.xx linuxandcompany.it - [06/Apr/2014:23:54:13 +0200] "GET /2014/01/squid-come-reverse-proxy/ HTTP/1.1" 200 10177 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.110 Safari/537.36"

Questo tipo di analisi si riesce a fare direttamente in linea con il perl. Iniziamo prima da una cosa cosa semplice: contiamo il numero di occorrenze dei vari return code per un dato sito:

cat linuxandcompany.it-08-04-2014.log|grep "linuxandcompany.it" |perl -ne '{@fields = split(/ /); $stat{$fields[8]}++;}END{while( ($key1, $value1) = each(%stat) ){print $key1. "\t" .$value1. "\n"}}'

che darà un risultato della forma:

304	16
200	397
302	4
301	2
404	2

Il comando inizia con un cat che passa ogni riga del file di log in stdout. Lo stdout viene intercettato da un grep che seleziona le righe del mio sito.
Le righe selezionate vengono poi passate al comando per che, come visto, con le opzioni ne applica il comando che segue ad ogni riga.
Il comando per è costituito da due blocchi: quello ordinario nella prima coppia di parentesi graffe e un secondo blocco identificato da END{…} che viene eseguito solo a conclusione del ciclo.

Il primo blocco inizia con uno split che separa la stringa in stdin in un array con le varie parole per elementi

@fields = split(/ /);

Tra gli slash è riportato il carattere scelto per spezzare la stringa.
Lo split avrebbe potuto anche essere scritto come

@fields = split(/ /, $_);

Utilizzo poi i campi per incrementare un hash di contatori che ha per chiavi i return code.

$stat{$fields[8]}++;

Il blocco finale si occuperà poi di visualizzare il risultato:

while( ($key1, $value1) = each(%stat) )
   {
   print $key1. "\t" .$value1. "\n"
   }

ed è costituito da un ciclo sulle coppie chiave, valore dell’hash.

In una situazione reale questo comando funziona però fino ad un certo punto perché le stringhe possono contenere spazi anche se non è frequentissimo. Un risultato molto migliore si ottiene combinando due split: un primo basato sulle virgolette, che racchiudono le stringe significative, ed un secondo basato sugli spazi da applicare alle sottostringhe:

cat linuxandcompany.it-08-04-2014.log|grep "linuxandcompany.it" |perl -ne '{@fields = split(/"/); @sub_fields = split(/ /, $fields[2]); $stat{$sub_fields[1]}++;}END{while( ($key1, $value1) = each(%stat) ){print $key1. "\t" .$value1. "\n"}}'

Si può poi complicare a piacere andando a fare la stessa statistica per ogni sito presente nel log. Sotto faccio un doppio report (per sito e complessivo).

cat linuxandcompany.it-08-04-2014.log |perl -ne '{@fields = split(/"/); @sub_fields1 = split(/ /, $fields[0]); @sub_fields2 = split(/ /, $fields[2]); $stat1{$sub_fields1[1]}{$sub_fields2[1]}++; $stat2{$sub_fields2[1]}++}END{while(($key1, $value1) = each(%stat1)){ while( ($key2,$value2) = each(%$value1) ){print $key1. "\t" . $key2."\t".$value2. "\n"}}; print "\n\n"; while( ($key1, $value1) = each(%stat2) ){print $key1. "\t" .$value1. "\n"}}'

Rimane evidente che questo è al limite di quello che ha senso fare con una linea di comando e non scrivendo un piccolo script.

Comandi analoghi si possono utilizzare ad esempio per contare le occorrenze delle pagine nel log.

Howto – virtualizzazione su Linux con KVM

La Virtualizzazione è un tool che torna sicuramente utile se si vogliono testare nuovi daemon e non si hanno a disposizione dei server da utilizzare come laboratorio. Torna utile sia che si vogliano testare altri sistemi operativi dato, che da la possibiltà di farlo senza dedicare a questi dell’hardware e senza le complicazioni del dual boot, sia che si vogliano testare dei demoni visto che permette di farlo in un ambiente isolato.

Virtualizzazione che è oltretutto interessante in se in quanto è anche uno strumento molto utilizzato in ambienti di produzione per quel che permette in termini di scalabilità, affidabilità e ottimizzazione.

Vedremo in questo post i passi necessari per predisporre il proprio PC ad ospitare dei server virtuali e gli step necessari per una prima istallazione.

La virtualizzazione è in linea di principio possibile su qualsiasi processore general-pourpose ma in pratica se si vogliono prestazioni decenti bisogna accertarsi che la propria CPU abbia le relative funzionalità hardware. Per farlo verifichiamo se sui flags della CPU è riportato svm o vmx.

grep svm /proc/cpuinfo
grep vmx /proc/cpuinfo

Andiamo ad installare i pacchetti necessri:

aptitude install qemu-kvm libvirt-bin virtinst

inoltre sarebbe utile il pacchetto virt-manager per avere un’interfaccia grafica di gestione delle macchine virtuali.

aptitude install virt-manager virsh virt-viewer

virt-manager è un’interfaccia grafica che permette di gestire le macchine virtuali sia per crearle sia per gestirne lo stato di avvio, sia per accedere ad una consolle. Ma di fatto permette una gestione molto basilare delle macchine virtuali.

In alternativa si può utilizzare virsh. Ad esempio

virsh -c qemu:///system list

darà la lista dei server attivi
Ma virsh può essere utilizzato anche come consolle lanciandolo senza parametri

$ virsh 
Benvenuti a virsh, il terminale interattivo per la virtualizzazione.

Digitate:  'help' per un aiuto sui comandi
       'quit' per uscire

virsh # help

Ad esempio per avere la lista delle Virtual Machines configurate

virsh 'list --all'

e per avviarne una

virsh start <nome VM>

ci si potrà poi connettere alla consolle ad esempio con

virt-viewer -c qemu:///system <nome VM>

controllo dei processi – supervisord

Parliamo oggi di supervisord, un sistema di controllo dei processi. Questi sistemi hanno la funzione di garantire che altri processi siano costantemente attivi. Possono essere quindi elementi importanti per garantire l’affidabilità di un servizio soprattutto nei casi in cui il daemon da controllare è sviluppato in casa e da, quindi, molta meno affidabilità di un daemon con migliaia di istallazioni.

Il primo passo è, come sempre, istallare supervisord. Lo possiamo fare dal sito del progetto o utlizzando il sistema di pacchetti della propria distribuzione. Su Debian o Ubuntu:

aptitude install supervisor

Ad istallazione effettuata si avrà il demone supervisord e il tool di controllo supervisorctl. Inoltre il demone può essere configurato per rispondere a chiamate html (di default sulla porta 9001) sia per monitoraggio via browser sia per gestione tramite API xml-rpc.

Per prima cosa esaminiamo la configurazione di default che arriva con il pacchetto Ubuntu:

; supervisor config file

[unix_http_server]
file=/var/run/supervisor.sock   ; (the path to the socket file)
chmod=0700                       ; sockef file mode (default 0700)

[supervisord]
logfile=/var/log/supervisor/supervisord.log ; (main log file;default $CWD/supervisord.log)
pidfile=/var/run/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
childlogdir=/var/log/supervisor            ; ('AUTO' child log dir, default $TEMP)

; the below section must remain in the config file for RPC
; (supervisorctl/web interface) to work, additional interfaces may be
; added by defining them in separate rpcinterface: sections
[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///var/run/supervisor.sock ; use a unix:// URL  for a unix socket

; The [include] section can just contain the "files" setting.  This
; setting can list multiple files (separated by whitespace or
; newlines).  It can also contain wildcards.  The filenames are
; interpreted as relative to this file.  Included files *cannot*
; include files themselves.

[include]
files = /etc/supervisor/conf.d/*.conf

Come avviene in moltissimi casi i parametri di configurazione sono raggruppati in paragrafi.

  • [unix_http_server]: contiene i parametri necessari a definire il socket unix per le connessioni a supervisord.
  • [supervisord]: configurazione vera e propria del daemon; file di log, file di lock.
  • [rpcinterface:supervisor]: serve ad abilitare l’interfaccia rpc
  • [supervisorctl]: parametri passati usati al client; ad esempio come connettersi al server se non specificato altrimenti
  • [include]: un elenco di file da includere. Tipicamente uno per ogni tipo di daemon da controllare con le specifiche direttive.

Come prima cosa attiviamo l’interfaccia web abilitando un’utenza ed una password. Aggiungiamo o modifichiamo quindi il paragrafo inet_http_server con i seguenti valori:

[inet_http_server]
port = 127.0.0.1:9001
username = user
password = 123

e riavviamo il daemon

/etc/init.d/supervisord restart

Possiamo ora accedere all’interfaccia web connettendoci in localhost sulla porta 9001 ed inserendo i dati dell’account quando richiesti.

Creiamo  ora uno script che utilizzeremo come daemon di test e configuriamo supervisord per gestirlo.

Editiamo quindi un file denominato test_supervisord.sh  con il nostro editor di riferimento ed inseriamoci il seguente codice:

#!/bin/sh
while [ true ]; do 
	ls -lh /tmp/
	rm /tmp/pippo
	touch /tmp/pippo
	sleep 5
done

cambiamo poi i permessi per renderlo eseguibile

chmod 700 test_supervisord.sh

Creiamo ora la configurazione su supervisord per dirgli di gestire questo daemon. Andremo ad inserire nel file /etc/supervisor/conf.d/test.conf il seguente contenuto

[program:test_supervisord]
command=/tmp/test_supervisord.sh
autostart=true
autorestart=true
stderr_logfile=/var/log/supervisor/test.err
stdout_logfile=/var/log/supervisor/test.log

dove, in un apposito paragrafo specifichiamo

  • [program:test_supervisord]: il nome del daemon da riportare sui vari report
  • command=/tmp/test_supervisord.sh: il comando da eseguire
  • autostart=true: se deve partire automaticamente all’avvio
  • autorestart=true: se deve essere riavviato in caso di interruzione
  • stderr_logfile=/var/log/supervisor/test.err: file di log in cui viene registrato l’output del daemon
  • stdout_logfile=/var/log/supervisor/test.log file di log in cui vengono riportato lo standard error del daemon

I parametri specificabili sono molti altri. Ad esempio

  • user: permette di specificare l’utente con cui il daemon viene eseguito
  • environment: accetta coppie chiave-valore che vengono trasformate in variabili di ambiente per l’esecuzione del daemon.

Per rendere attiva la nuova configurazione si può procedere con uno tra

kill -HUP "PID"
supervisorctl reread && supervisorctl update

o anche attraverso l’interfaccia web visto che l’abbiamo attivata.

supervisorctl rimane comunque l’interfaccia di eccellenza di questo sistema.

Al suo avvio presenta l’elenco dei demoni configurati ed il loro stato. Ed il comando help mostra cosa può essere fatto all’interno di questa command line.

root@nb:/etc/supervisor/conf.d# supervisorctl 
test_supervisord                 RUNNING    pid 8443, uptime 0:03:40
supervisor> help

default commands (type help ):
=====================================
add    clear  fg        open  quit    remove  restart   start   stop  update 
avail  exit   maintail  pid   reload  reread  shutdown  status  tail  version

supervisor>

stop, start, restart e status serviranno ad esempio a gestire i vari daemon. reread e update li abbiamo già visti in azione. Quit permette di lasciare questa shell.

redis – nosql repository

Nell’ articolato panorama dei database nosql riveste un certo interesse Redis. Questo database ancora immaturo per gli aspetti di scalabilità e di alta affidabilità è sicuramente interessante per le prestazioni.

Redis, generalmente indicato come respository chiave/valore, è in realtà in grado di gestire 5 tipi di dato differente ognuno adatto ad ottimizzare le prestazioni di specifiche operazioni.  Redis fa infatti della velocità il suo punto di forza.

In Redis possono essere immagazzinati:

  • stringhe
  • hash
  • liste
  • set
  • sorted set

Redis dispone anche della possibilità di scrivere delle stored procedure in LUA.

Questo database lavora mantenendo tutti i dati in memoria ma al tempo stesso è in grado di garantire la persistenza dei dati attraverso due meccanismi, entrambi disattivabili:

  • il salvataggio periodico del dataset su disco triggerabile sia su base temporale sia sulla base del numero di modifiche
  • la produzione di log delle modifiche

Redis dispone inoltre di un sistema di replica che permette di generare copie di un repository a caldo. Ci sono poi altri tool legati alla clusterizzazione ma che al momento attuale non sono in verisone stabile.

Per un’istallazione di test la via più comoda è come sempre ricorrere ai pacchetti della propria distribuzione:

aptitude install redis-server

Su debian è opportuno ricorrere ai pacchetti di backport visto che come tutti i sistemi nosql, Redis è un prodotto piuttosto giovane e ci sono spessp importanti fix nelle nuove versioni.

Sul manuale del prodotto sono riportate due considerazioni di cui è meglio tener conto:

  • è bene che il server disponga di molto swap perché in alcune situazioni Redis arriva anche a raddoppiare l’uso della memoria e nel caso questa finisca il kernel va in protezione ed uccide il processo.
  • Redis ha problemi con l’uso standard di Linux della memoria virtuale; viene quindi consigliato di impostare ad uno vm.overcommit_memory

Redis può essere fatto lavorare in una modalità analoga a quella di una cache impnendogli di sovrascrivere i TTl degli oggetti se si presenta la necessità di liberare memoria.

Con il pacchetto viene istallato anche un client

redis-benchmark
redis-cli

Il primo permette una prima verifica del funzionamento del sistema e da  un’idea delle prestazioni del sistema. Il secondo è un client che permette di interrogarlo.
Ovviamente l’accesso più naturale ad un sistema di questo tipo sono le librerie di qualche linguaggio.

Ad ogni modo una volta dentro si possono ottenere informazioni sullo stato del daemon con il comando info:

$>redis-cli
redis 127.0.0.1:6379> info

oppure si possono gestire i dati con i molti comandi possibili:

redis 127.0.0.1:6379> set nome:pippo '{"città":  topolinia, "razza": cane}'
redis 127.0.0.1:6379> get nome:pippo

get e set sono alcuni dei comandi relativi alle stringe. Per la gestione di altri tipi di dati ci sono i comandi relativi.

 

Primi passi in IPv6

Questo articolo lo sto scrivendo connesso all’IP

2001:41d0:1:1b00:94:23.64:3

. Quello che si ottiene con un

dig -t AAAA linuxandcompany.it

Non è mica poco!

Alla fine cercando in rete ho trovato che da rete Fastweb è piuttosto semplice, anche se le informazioni è stato necessario metterle insieme da più fonti.

Prima di tutto l’IPv6 non è nativo ma la rete fastweb, come riportato qui, contiene il server

tsp.ipv6.fastweb.it

che permette di fare un tunnel verso la rete IPv6.

Per usare questo client si piò utilizzare il client gogoc sviluppato in gogo6. Da sito si scaricano solo i sorgenti e almeno sulla mia versione di ubuntu la compilazione non va a buon fine ma ho scoperto che il client è tra i pacchetti di ubunto e si chiama gogoc; basta quindi un

aptitude install gogoc

L’ultimo step è inserire qualche dato in

/etc/gogoc/gogoc.conf

ovvero

server=tsp.ipv6.fastweb.it
auth_method=anonymous

Dopo di che

/etc/init.d/gogoc start
/etc/init.d/gogoc status
ip add sh

saranno sufficienti per attivare il tunnel IPv6 e per verificarne lo stato.

Potremo poi verificare l’avvenuta attivazione dai siti

http://test.ipv6.fastweb.it/
http://www.ipv6.fastweb.it/

Se si ha poi un qualche tool che visualizza l’IP del sito che si sta visualizzando sul browser, e si naviga dopo averlo riavviato, si cominceranno a vedere molti accessi su questo protocollo di trasporto, che ha priorità più alta. Dovreste vederlo su linuxandcompany.it ma anche su qualche sito minore come www.google.com.