Bariere. Comutatoare. SBC-uri. Taxonomia Flynn

Bariere

De multe ori un grup de thread-uri sau procese trebuie sa ajunga toate intr-un anumit punct si numai dupa aceea executia poate continua. Mecanismul de sincronizare potrivit pentru asemenea cazuri este bariera. In python nu avem bariera predefinita (asa cum avem, de exemplu, Lock, Semaphore, Condition sau Event), dar putem s-o implementam cu ajutorul celorlalte mecanisme de sincronizare.

Iata o bariera implementata cu semafoare:

  1. bariera = threading.Semaphore(value=0)
  2. regcritica = threading.Semaphore(value=1)
  3. threads = 20
  4. n = threads
  5.  
  6. def barrier():
  7. global bariera,regcritica,n,threads
  8. #p1
  9. regcritica.acquire()
  10. n -= 1
  11. if n == 0:
  12. for i in range(threads):
  13. bariera.release()
  14. regcritica.release()
  15. #p2
  16. bariera.acquire()
  17. #p3

Bariera reentranta

Adesea barierele sunt folosite in bucle. Thread-urile sau procesele executa anumite instructiuni intr-un ciclu, iar rezultatele iteratiei curente pot fi folosite in iteratia urmatoare. Dupa fiecare iteratie, se face o sincronizare cu bariera. Bariera prezentata mai sus nu se poate folosi in astfel de cazuri. Dupa prima iteratie, toata executia s-ar bloca la bariera, si nici un thread nu ar mai putea trece mai departe, caci n-ul are valoarea 0 de la inceput.

Avem nevoie de o bariera reentranta (reutilizabila).

Exemplu gresit de bariera reentranta (s-a incercat reinitializarea contorului din bariera):

barier.py
  1. bariera = threading.Semaphore(value=0)
  2. regcritica = threading.Semaphore(value=1)
  3. threads = 20
  4. n = threads
  5.  
  6. def barrier():
  7. global bariera,regcritica,n,threads
  8.  
  9. #p1
  10. regcritica.acquire()
  11. n -= 1
  12. if n == 0:
  13. for i in range(threads):
  14. bariera.release()
  15. n = threads
  16. regcritica.release()
  17. #p2
  18. bariera.acquire()
  19. #p3

De ce nu functioneaza corect acest exemplu?

Vedeti ce se intampla cand rulati un program care foloseste aceasta bariera intr-o bucla.

bariera3.py
  1. def barrier3(index):
  2. global bariera, bariera2, regcritica, n, n2, threads
  3. #p1
  4. regcritica.acquire()
  5. n -= 1
  6. if n == 0:
  7. for i in range(threads):
  8. bariera.release()
  9. n2 = threads
  10. regcritica.release()
  11. #p2
  12. print "b1 " + str(index)
  13. bariera.acquire()
  14.  
  15. regcritica.acquire()
  16. n2 -= 1
  17. if n2 == 0:
  18. for i in range(threads):
  19. bariera2.release()
  20. n = threads
  21. regcritica.release()
  22. #p3
  23. print "b2 " + str(index)
  24. bariera2.acquire()
  25. #p4

Comutatoare

Exista mai multe tipuri de comutatoare ierarhice si neierarhice:

  • Switch poarta: Sp
  • Comutatoar duplex: Sduplex=(1a, nb, c=1, n Sp)
  • Comutator crosspoint: Scp=(ma, nb, c=1, (m+n) Sp)
  • Comutator crossbar: Scb=(ma, nb, c=min(m,n), m*n Sp)
  • Switch trunchi K: Stk=(ma, nb, c=k, (m+n)*kSp)
  • Switch crossbar neierarhic: Scbn=(ma, c=m/2, m*(m-1)/2 Sp)
  • Switch trunchi K neierarhic: Stkn=(ma, c=min(m/2,k), m*k Sp)

Cel mai simplu tip de comutator, care este folosit pentru a conecta 2 resurse de acelasi tip, sau tipuri diferite, este comutatorul de tip poarta: Sp . Deoarece acest comutator ofera o singura conexiune, se spune ca are concurenta 1.
Comutator de tip poarta

In continuare, pentru urmatoarele comutatoare, capacitatea de a conecta un set de resurse cu alt set de resurse se numeste concurenta si se masoara ca multiplu de concurenta realizata pentru Sp (adica 1). Complexitatea unui alt comutator se masoara in numarul de Sp-uri necesare pentru a simula functionalitatea acestuia, deoarece Sp este comutatorul de baza.

Comutatorul duplex permite conectarea unei resurse de tip a cu mai multe resurse de tip b, existand la un moment dat o singura conexiune. Astfel, caracteristicile acestui comutator sunt: Sduplex=(1a, nb, c=1, nSp).
Comutator de tip duplex

Comutatorul de tip crosspoint permite conectarea a n resurse de tip a cu m resurse de tip b . Un exemplu in acest sens este o magistrala care are pe de o parte niste unitati de prelucrare de date si pe de alta parte niste memorii. Deoarece exista un singur mediu de comunicare, valorile caracteristice sunt: Scp=(ma, nb, c=1, (m+n)Sp), deci concurenta este 1.
Comutator de tip crosspoint

Switch-ul crossbar permite conectarea in acelasi timp a mai multor resurse de tip a cu mai multe resurse de tip b, prin comutarea Sp-urilor astfel incat la un moment dat o resursa de tip a sau b nu este conectata la mai multe resurse de tip b, respectiv a. Caracteristicile sunt: Scb=(ma, nb, c=min(m,n), m*nSp). Aceasta este cea mai “scumpa” implementare de comutator, deoarece necesita m*n comutatoare de baza (Sp). Despre utilizarea unui switch de tip crosssbar intr-o arhitectura multiprocesor puteti citi aici Comutator de tip crossbar

O varianta mai ieftina din punctul de vedere al numarului de comutatoare de baza cu care se poate echivala, este comutatorul de tip trunchi k, care este mai des intalnit in practica ca cel anterior. Acesta conecteaza mai multe resurse de un tip cu mai multe resurse de alt tip, numarul de conexiuni posibile la un anumit moment fiind dat de numarul de linii interne ale comutatorului (k). Caracteristicile sunt: Stk=(ma, nb, c=k, (m+n)*kSp).
Comutator de tip trunchi K

Comutatoarele neierarhice permit conectarea oricarei resurse cu oricare alta in limita liniilor disponibile de catre comutator.

Utilizarea unui Switch de tip Crossbar intr-o Arhitectura Multiprocesor

O solutie pentru a imbunatati performantele unui sistem de calcul poate consta in adaugarea mai multor procesoare. In cazul unei arhitecturi de sistem elaborata cu atentie, se poate opta pentru a maximiza cantitatea de resurse disponibile pentru fiecare procesor si, in acelasi timp, pentru minimizarea numarului de conflicte dintre procesoare pentru accesul la resurse. Arhitectura sistemului este factorul principal care determina un castig de performanta in cazul adaugarii mai multor procesoare.

Magistralele constituie un concept simplu si nu necesita un efort deosebit pentru implementare. Ideea de baza consta in conectarea mai multor procesoare, folosind o aceeasi cale electrica, pentru a avea acces la o anumita resursa, de exemplu memoria principala. Numarul de procesoare ce pot fi conectate la o magistrala este insa marginit de catre limitele magistralei. Fiecare procesor trebuie sa foloseasca mai ales cache-ul local pentru a evita inundarea cu trafic a magistralei.

Pentru un acces optim la resursele sistemului, se folosesc multiple cai paralele catre acestea. Fiecare procesor poate comunica cu un switch la viteze mari de transfer. Procesoarele trebuie sa negocieze accesul doar in cazul in care concureaza la o aceeasi resursa. Un switch crossbar poate reduce timpii pentru accesul la magistrala si deci poate asigura o metoda pentru dezvoltarea de masini vectoriale performante.

Pentru un server care foloseste o magistrala partajata, exista, in mod evident, o limitare in a adauga noi procesoare. Fiecare procesor trebuie sa “negocieze” o banda de acces limitata, degradand performantele globale ale sistemului. Magistrala devine o resursa pentru care procesoarele trebuie sa concureze.

In cazul utilizarii unui switch de tip crossbar pentru conectarea procesoarelor la resursele existente (memorii, dispozitive I/O, etc.), fiecare procesor are o rata de transfer asigurata de catre switch, permitandu-i-se un acces independent la magistrala. Procesoarele interactioneaza doar in cazul in care acceseaza o aceeasi resursa diferita de magistrala. Introducerea switch-ului contribuie la imbunatatirea performantei intregului sistem si la cresterea scalabilitatii acestuia.

In partea dreapta a figurii se poate observa modul in care magistrala realizeaza o “strangulare” a traficului dintre procesoare si resursele partajate de acestea. Switch-ul crossbar introdus in arhitectura sistemului (partea stanga a figurii) garanteaza o rata de transfer intre un procesor si o alta resursa la care acesta se poate conecta. Conflictele apar in acest caz doar in cazul in care mai multe procesoare solicita accesul la o aceeasi resursa, fiind excluse conflictele generate de partajarea caii de comunicatie.
Exemplu switch crossbar

Single Board Computer (SBC)

Istorie

Acest document prezinta ce sunt SBC-urile, istoria lor, precum si cele mai uzuale standarde utilizate pentru SBC: PC/104 si EBX.

Un alt document despre evolutia SBC-urilor se poate gasi aici.

Exemple de alte arhitecturi

Cateva exemple de arhitecturi SBC sunt urmatoarele:

Costuri

Deoarece exista diverse tipuri de imlementari de SBC-uri, de asemenea exista si mai multe categorii de preturi.
La referintele puse la dispozitie puteti consulta o lista de preturi pentru SBC-uri cu diferite functionalitati, oferite de Industrologic.

TC51 Single Board Computer

Structura de SBC-uri conectate pe o magistrala

Acest document va ofera suportul teoretic pentru o structura de SBC-uri cuplate pe o magistrala. O implementare (PowerMAXION) care respecta structura enuntata de acest document este oferita de firma Concurrent. Puteti de asemenea studia detaliile legate de bus-ul VME.

Taxonomia Flynn

Ce este o taxonomie

O taxonomie se refera la o clasificare ierarhica a unor obiecte, mai exact reprezinta principiile care stau la baza clasificarii. In principiu orice clasa de obiecte poate fi clasificata dupa o anume taxonomie. Mai multe detalii despre termenul taxonomie puteti gasi spre exemplu in explicatia din Wikipedia.

Clasificarea Flynn

Clasificarea Flynn este una din cele mai cunoscute taxonomii. Flynn a propus-o in 1966. Aceasta clasificare se face in functie de fluxul de date si fluxul de instructiuni. Fluxul de date reprezinta secventa de operanzi manipulata de procesor. Fluxul de instructiuni reprezinta secventa de instructiuni executata de procesor.

In functie de cum pot fi fluxul de date si fluxul de instructiuni rezulta 4 categorii in care se pot imparti sistemele de calcul:

  • SISD - Single Instruction Stream, Single Data Stream
  • SIMD - Single Instruction Stream, Multiple Data Stream
  • MISD - Multiple Instruction Stream, Single Data Stream
  • MIMD - Multiple Instruction Stream, Multiple Data Stream

SISD

Este calculatorul clasic von Neumann.
Von Neumann

Fiecare instructiune aritmetica initiaza o operatie asupra unei element (data) luat dintr-un set unic de elemente deci exista un flux unic de instructiuni si un flux unic de date.
Arhitectura SISD

SIMD

Este in general un sistem de calcul de tip vectorial unde toate PA executa aceeasi instructiune.
Arhitectura SIMD

Calculatoarele de tip SIMD au o singura unitate de procesare (Ucmd) si mai multe unitati de prelucrare (PA). Prima masina operationala de acest tip a fost ILLIAC-IV (un proiect al DARPA, Burroughs Corporation si University of Illinois Institute for Advanced Computation (mai multe informatii despre ILLIAC-IV gasiti la thefreedictionary.com ).

Unitatea de comanda este cea care se ocupa de preluarea (fetch) si interpretarea instructiunilor. Atunci cand se intalneste o instructiune aritmetica sau o instructiune de prelucrare de date, aceasta trimite instructiunea la toate unitatile de prelucrare, care vor executa toate aceeasi operatie (evident asupra unor date diferite, proprii fiecarui procesor). Spre exemplu instructiunea ar putea fii: add R1,R2. Fiecare PA aduna continutul registrului propriu R2 la R1. Pentru a da o flexibilitate in implementarea algoritmilor, unul sau mai multe PA pot fi dezactivate prin folosirea de masti. Prin urmare, la fiecare instructiune un PA poate fi deactivat (caz in care nu face nimic), sau activ, ceea ce inseamna ca executa aceeasi operatie pe care o fac toate PA activate.

Unul din avantajele acestui stil de organizare a unui calculator paralel este economisirea de “logica”. In general 20 pana la 50% din “logica” de pe un procesor obisnuit este folosita pentru control, mai exact, aducerea (fetch) decodarea si planificarea instructiunilor. Restul este folosita pentru registrii,pentru cache si pentru implementarea procesarii de date (sumatoare, multiplicatoare, etc). La SIMD, o singura unitate de comanda aduce (fetch) si proceseaza instructiuni, prin urmare mai multa “logica” poate fi folosita pentru circuitele aritmetice si registrii.

Problema sincronizarii este lipsita de relevanta in cazul sistemelor SIMD: procesoarele ori executa aceeasi instructiune in acelasi timp ori nu executa nimic. Se poate observa existenta unei bariere implicite dupa fiecare instructiune. Ucmd trimite o instructiune spre executie si asteapta ca toate procesoarele activate sa-si termine treaba.

Modul de functionare al sistemelor vectoriale se regaseste si in interiorul unor sisteme uniprocesor. La Intel intalnim 2 seturi de instructiuni pentru operatiile pe vectori : MMX (o varianta mai veche) si SSE (SSE1, SSE2, SSE3). La AMD exista 3DNow!, iar la PowerPC AltiVec.

Mai multe informatii legate de arhitecturile SIMD gasiti aici.

MISD

Sunt doar cateva masini in aceasta categorie, si nici una nu a avut un succes comercial, sau vreun impact din punct de vedere stiintific. Un sistem care intra in aceasta categorie (MISD) este un vector sistolic, care este o retea de mici elemente computationale conectate intr-o retea grila (grid). Toate elementele sunt controlate de un ceas global. La fiecare ciclu, un element va citi o valoare de la unul din vecini, efectueaza o operatie simpla (spre exemplu adunarea valorii sosite la la o valoare existenta deja) si pregateste o valoare spre a fi scrisa la un vecin la urmatorul pas.

Un alt exemplu ar putea fi considerat un procesor care prelucreaza in banda de asamblare (pipeline) pe motiv ca fiecare pas din banda de asamblare corespunde unei operatii diferite efectuata asupra datelor.

Un exemplu de utilizare ar putea fi compararea a diversi algoritmi pe aceleasi date de intrare.
Arhitectura MISD

MIMD

Aceasta categorie de calculatoare este cea mai diversificata dintre toate cele 4 categorii. Include masini cu procesoare si memorii proiectate special pentru arhitecturile paralele, masini paralele construite cu procesoare obisnuite conectate impreuna si altele. Odata cu imbunatatirea comunicarii pe retea si cu dezvoltarea softurilor care permit comunicatia intre calculatoare se poate considera ca o retea locala de calculatoare obisnuite intra in categoria masinilor MIMD.

Arhitectura MIMD

Calculatoarele cu doua sau mai multe procesoare independente exista de foarte mult timp. Unul dintre primele a fost vandut de Burroughs Corporation in anii 1970 si avea 2 procesoare (se numea B6700). Cu toate astea, cele 2 procesoare erau foarte rar puse sa lucreze la acelasi job. Calculatoarele multiprocesoare actuale, sunt proiectate sa aibe un paralelism la nivel de job, spre exemplu fiecare executa o aplicatie diferita. Procesare parelela, in sensul folosirii a 2 sau mai multe procesoare pentru executareea unei singure aplicatii este un subiect de care s-au ocupat cercetatorii inca din anii 70. Procesoarele paralele comerciale au inceput sa fie larg raspandite in anii 80. La inceputul anilor 90 deja au inceput sa depaseasca procesoarele SIMD in materie de putere de calcul.

O subclasificare a sistemelor MIMD dupa retelele de interconectare este urmatoarea (Tanenbaum):

  • calculatoare multiprocesor cu memorie partajata in 2 variante UMA si NUMA (comunicarea este realizata prin instructiunile Load si Store); viteza de comunicare de ordinul zecilor de ns ;
  • sisteme multicalculator (clustere) comunicatia este realizata prin instructiunile Send si Receive iar viteza maxima este de ordinul zecilor de microsecunde;
  • sisteme distribuite - comunicatia este realizata peste retele cu viteza de transfer mica de ordinul milisecundelor.

Viteza retelei de interconectare pentru MIMD impune restrictii pentru setul de aplicatii care pot fi rulate pe sistem. Evident cu cat reteaua de interconectare pentru MIMD are o viteza mai mica cu atat setul de aplicatii care pot fi rulate pe acel sistem este mai redus.

Rezumat

Posibilitatile de calcul ale sistemelor prezentate mai sus sunt rezumate in aceasta figura:

Overview Flynn

Alte taxonomii

Cu toate ca taxonomia Flynn este cea mai folosita taxonomie pentru clasificarea calculatoarelor, aceasta are si neajunsuri. Categoria MISD nu prea are reprezentanti, si este foarte dificil de incadrat intr-o categorie procesoarele vectoriale. O alta slabiciune este faptul ca la categoria MIMD, toate procesoarele sunt puse la un loc, neluand in seama cum sunt conectate sau modul in care vad memoria. Din moment ce aceste caracteristici pot avea efecte importante asupra performantei, ar fi de dorit o taxonomie care sa reflecte aceste diferentieri.

Shore a oferit o taxonomie similara, dar a impartit categoria SIMD in patru subcategorii.

O alta incercare de detaliere a taxonomiei Flynn a facut-o Hwang, care a impartit categoria MIMD in sisteme cu memorie partajata (shared memory), sisteme distribuite si sisteme reconfigurabile. Aceasta taxonomie amesteca tipul de organizare a memorie cu modul de organizare a comunicarii, deci nu e o taxonomie in adevaratul sens al cuvantului. Bell a impartit MIMD in sisteme cu memorie partajata si sisteme fara memorie partajata. O alta adaugire facuta la taxonomia Flynn care a devenit foarte populara este SPMD (Single Program / Multiple Data). Dar asta e mai mult un stil de programare decat o arhitectura. Fizic vorbind este o arhitectura MIMD, pentru ca exista mai multe procesoare independente, fiecare avand sectiunea lui de date si de program. Acelasi program este executat de fiecare procesor, si procesoarele se sicronizeaza periodic. Acesta este un mod mult mai simplu de a folosi un sistem MIMD, pentru ca nu mai e nevoie sa se gestioneze mai multe fluxuri de instructiuni. Ofera mai multa flexibilitate decat SIMD pentru ca procesoarele pot fi in stadii diferite ale executiei.

Informatii suplimentare despre SIMD-uri

Arhitecturile SIMD (Single Instruction stream, Multiple Data stream) ocupa un rol esential in lumea calculatoarelor paralele. Capacitatea lor de a manipula date vectoriale si matriciale in timpi mici a creat o cerere deosebita in domenii precum cercetarea cancerului si previziunile meteo. Puterea acesor masini devine evidenta cand dimensiunea vectorului de prelucrat este echivalenta cu numarul de procesoare aritmetice. In acest caz adunarea si multiplicarea elementelor vectorului de intrare se pot realiza simultan. In figura operatiile intre componentele celor 2 vectori se executa in paralel, fiecare de cate o unitate de prelucrare aritmetica. Toate arhitecturile SIMD contin o unitate de comanda si mai multe unitati de prelucrare. Comenzile date de unitatea de comanda se executa pentru toate unitatile de prelucrare active. Din punct de vedere constructiv, masinile SIMD se impart in:

  • Arhitecturi SIMD reale
    • Memorie distribuita
    • Memorie partajata
  • Arhitecturi SIMD Pipeline

Arhitecturi SIMD cu memorie distribuita

O masina SIMD simpla este cea in care fiecare unitate aritmetica are memoria sa proprie si este singura care are acces la ea.

In aceasta arhitectura fiecare procesor primeste instructiuni si executa operatii asupra datelor din memoria proprie. Dezavantajul este necesitatea de a avea datele structurate in memoriile individuale ale procesoarelor inca inainte de incputul programului. O arhitectura SIMD reala ce contine o singura unitatea de control si multiple elemente de procesoare (unitati de executie-EU). In acest model, unitatile aritmetice sunt “sclavii” unitatii de comanda. EU nu pot primi sau interpreta interpreta instructiuni ci au simple capacitati de adunare, scadere, inmultire si impartire. Deoarece fiecare EU are acces doar la propria sa memorie daca are nevoie de date stocate in memoria altei EU trebuie ca CU(Command Unit) sa realizeze transferul informatiei. Avantaj: este usoara adaugarea memoriei suplimentare sau a altor CU Dezavantaj: ciclurile risipite de CU in transferul datelor de la o EU la alta Arhitecturi SIMD cu memorie partajata O imbunatatire a primei arhitecturi se poate face prin permiterea schimburilor directe de date intre procesoarele aritmetice sau intre procesoare si memorii. Cea mai comuna metoda este de a adauga un cross-bar switch. Astfel fiecare EU isi poate partaja memoria fara sa faca apel la CU.

Acest tip de arhitectura SIMD este evident superior primului. Dezavantaj: dificultatea de a adauga memorie suplimentara.

Arhitecturi SIMD Pipeline

In aceste masini, unitatile de executie sunt plasate intr-un pipeline. Pipeline-ul preia diferite fluxuri de instructiuni si realizeaza operatiile unei unitati aritmetice printr-o procedura de tip FIFO. Pentru a obtine avantaje din pipeline, datele ce trebuie prelucrate vor fi stocate in module de memorie diferite astfel ca pipeline-ul sa fie alimentat cu date cat mai repede cu putinta. Avantaje: viteza si eficienta oprelucrarii datelor (in cazul in care conditia de mai sus este indeplinita) Obs: aceasta nu este o arhitectura SIMD pura

Taskuri

1. Implementarea unei bariere folosind conditii

Implementati o bariera folosind variabile conditie (Condition).

2. Implementarea unei bariere reentrante

Implementati o bariera reentranta, pornind de la exemplul cu bariera “de unica folosinta” sau de la alta idee personala.

3. Teacher's choice

task.py
  1. """
  2. Proposed by VladS
  3.  
  4. We have a system with 3 types of entities: Clients, Servers and a FrontEnd.
  5. The Clients send requests from time to time to the FrontEnd using the FrontEnd's addRequest method. If the FrontEnd has reached its maximum number of requests that are in the processing phase the Client will block until a request is deleted.
  6. The FrontEnd picks one request from time to time and forwards it to a Server. This happens in the sendARequest method. The FrontEnd maintains a list of the requests that have been sent to Servers and have not been processed (sentReqList).
  7. The Servers process requests from time to time (processARequest method). After a request is processed the FrontEnd is notified via the delRequest method and the request is removed from the FrontEnd's request list.
  8.  
  9. 1) Add synchronization mechanisms so that the system behaves as specified and no race conditions occur.
  10. 2) Replace the while + sleep mechanism from the FrontEnd's and the Server's run method with an event / condition based mechanism.
  11.  
  12. """
  13.  
  14. from threading import *
  15. import random
  16. import time
  17. import sys
  18.  
  19.  
  20. stdoutLock = Lock()
  21. def safe_print(buffer):
  22. global stdoutLock
  23. stdoutLock.acquire()
  24. print buffer
  25. sys.stdout.flush()
  26. stdoutLock.release()
  27.  
  28. class Client(Thread):
  29. def __init__(self, name, frontEnd):
  30. Thread.__init__(self)
  31. self.name = name
  32. self.frontEnd = frontEnd
  33.  
  34. def populateReqList(self, nrReqs, nrServers):
  35. self.reqList = []
  36. for i in range(nrReqs):
  37. serverNr = int(random.random() * nrServers)
  38. reqNr = int(random.random() * 100)
  39. request = "S" + str(serverNr) + "R" + str(reqNr)
  40. self.reqList.append(request)
  41.  
  42. def run(self):
  43. while 1:
  44. if len(self.reqList) == 0:
  45. break
  46. time.sleep(random.random() * 3)
  47. self.frontEnd.addRequest(self.reqList.pop())
  48. print self.name, "has finished"
  49.  
  50. class FrontEnd(Thread):
  51. def __init__(self, name, maxReqs, serverList):
  52. Thread.__init__(self)
  53. self.name = name
  54. self.maxReqs = maxReqs
  55. self.reqList = []
  56. self.sentReqList = []
  57. self.serverList = serverList
  58.  
  59. def addRequest(self, request):
  60. self.reqList.append(request)
  61. safe_print(self.name + " added request " + request)
  62.  
  63. def delRequest(self, request):
  64. self.reqList.remove(request)
  65. self.sentReqList.remove(request)
  66. safe_print(self.name + " deleted request " + request)
  67.  
  68. def sendARequest(self):
  69. for elem in self.reqList:
  70. if elem not in self.sentReqList:
  71. self.sentReqList.append(elem)
  72. serverIndex = int(elem[1:elem.find("R")])
  73. self.serverList[serverIndex].addRequest(elem)
  74. safe_print("Sent " + elem + " to S" + str(serverIndex))
  75. break
  76.  
  77. def run(self):
  78. while 1:
  79. time.sleep(random.random() * 2)
  80. self.sendARequest()
  81. continue
  82.  
  83.  
  84. class Server(Thread):
  85. def __init__(self, name, frontEnd):
  86. Thread.__init__(self)
  87. self.name = name
  88. self.reqList = []
  89. self.frontEnd = frontEnd
  90.  
  91. def addRequest(self, request):
  92. self.reqList.append(request)
  93. safe_print(self.name + " added request " + request)
  94.  
  95. def processARequest(self):
  96. if (len(self.reqList) > 0):
  97. request = self.reqList.pop()
  98. safe_print(self.name + " processed request " + request)
  99. self.frontEnd.delRequest(request)
  100.  
  101. def run(self):
  102. while 1:
  103. time.sleep(random.random() * 4)
  104. self.processARequest()
  105. continue
  106.  
  107. if __name__ == "__main__":
  108. nrServers = 3
  109. nrClients = 5
  110. nrClientReqs = 5
  111. maxFrontEndReqs = 3
  112. clientList = []
  113. serverList = []
  114. #instantiate FrontEnd
  115. frontEnd = FrontEnd("F", maxFrontEndReqs, serverList)
  116. #instantiate Servers
  117. for i in range(nrServers):
  118. serverList.append(Server("S" + str(i), frontEnd))
  119. #instantiate Clients
  120. for i in range(nrClients):
  121. clientList.append(Client("C" + str(i), frontEnd))
  122. #populate client request lists
  123. for client in clientList:
  124. client.populateReqList(nrClientReqs, nrServers)
  125. #start everyone
  126. frontEnd.start()
  127. for server in serverList:
  128. server.start()
  129. for client in clientList:
  130. client.start()
asc/lab3/index.txt · Last modified: 2013/03/05 11:54 by emil.slusanschi
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0