Reserva de places d’Avió¶
Ens demanen dissenyar un sistema de reserves de les places d’un avió. Les reserves les efectuen un conjunt d’agències de viatges que atenen i gestionen les peticions que li arriben dels clients.
Les dades de les reserves de seients de l’avió es guarden en una base de dades única i centralitzada. Cada agència pot accedir a aquesta base de dades de forma concurrent. Quan s’atén la petició d’un client es cerca una plaça lliure, i si se’n troba, es reserva la plaça guardant en la base de dades, el nom del client i la data i hora en que es fa la reserva.
Per realitzar el sistema podrem comptar amb el disseny de la classe Avió:
La classe Avio¶
La classe Avió, especificada a continuació, està
implementada al fitxer avio.py. Representa
avions amb un cert nombre de files i sis seients per fila (lletres “A”
a “F”). Cada seient està identificat pel seu número de fila (un enter
entre 1 i el nombre de files totals de l’avió) i una lletra entre
“A” i “F”. La referència d’un seient serà una etiqueta (str)
amb primer el número, i després la lletra. Per exemple, "3B", "25C", …
- class avio.Avió(identificador, nFiles=10)¶
En el cas que no existeixi, crea una base de dades al disc amb el nom de l”
identificador(str) amb l’extensió.db. En el cas que aquesta existeixi, no la toca. El paràmetrenFilesés el nombre de files de 6 seients que tindrà el nou avió. En el cas que la base de dades existeixi, no es tindrà en comptenFiles, atès que la base de dades té el seu nombre de files propi.Atributs
- nFiles¶
Nombre de files de 6 seients de l’avió.
- lliure(seient):
Retorna True si la plaça de l’etiqueta seient (
strdel tipus"NL"i \(0 \leq N \lt nFiles\) i \('A' \leq L \leq 'F'\)) està lliure. Retorna False en cas contrari. Un seient lliure és un seient que no té cap nom assignat.
- places()¶
Retorna el nombre total de seients de l’avió.
Aquesta classe suporta les operacions següents:
Operació
Resultat
a[s]Retorna un tuple essent el primer element el nom de la persona que ocupa el seient de l’etiqueta s (
str) del tipus"NL"i \(0 \leq N \lt nFiles\) i \('A' \leq L \leq 'F'\)a[s] = (nom, data)Assigna el tuple composat per un nom (
str) i una data (datetime.datetime) al seient d’etiqueta s (str) del tipus"NL"i \(0 \leq N \lt nFiles\) i \('A' \leq L \leq 'F'\)Aquesta classe suporta la funció
iter()que retorna un iterador d’etiquetes de seients de l’avió ("1A", "1B", ..., "1F", "2A", "2B", ...).Proveu d’utilitzar aquesta classe. Disposeu d’un doctest per a inspirar-vos a
test-avio.txt. Mireu la implementació (avio.py) i observeu que la classe té atributs de classe, i altres mètodes per ús intern. La classeAvióestà connectada a una base de dades que guarda informació sobre els seus seients. La gestió de la base de dades es fa usant el mòdulmòdul sqlite3. Cada operació de lectura, escriptura de l’estat d’un seient es guarda sempre a la base de dades que hi ha al disc per no perdre cap dada en el cas que hagi una fallida d’alimentació elèctrica.
El plantejament del disseny serà el següent:
Dissenyar en el fitxer
agencia.pyun objecte fil anomenat Agència que donat un objecte de classeAvióavió, una llista de noms dels clients procedirà a fer la reserva de seients de l”avió. Mantindrà un registre de les reserves que ha pogut satisfer.Dissenyar en el fitxer
reserves.pyel programa principal que crearà un objecte de la classeAvió. Donat un nombre d’agències crearà un fil Agencia per cada agència a considerar, i els engegarà i esperarà la finalització de tots els fils. Farà una estadística per comprovar que no ha hagut cap sobre-reserva comprovant els registres de cada fil i que cap fil ha estat en inanició (comprovant el nombre de reserves que ha fet). Finalment farà un llistat de tots els clients ordenat per data de reserva.
Agencia¶
En el fitxer agència agencia.py es dissenyarà la classe Agència que heretarà de la classe threading.Thread del mòdul threading:
- class agencia.Agència(nomAg, avió, peticions)¶
Atributs
- avió¶
L’avió on s’han de fer les reserves de places (
Avió)
- reserva¶
Diccionari (
dict) amb els noms de cliens com a claus, i la referència del seient (str) com a valor.
Mètodes
- run()¶
Per cada petició de client, es cercarà si hi ha una plaça lliure en l’avió. En el cas que hagi la plaça lliure es reservarà la plaça a nom del client i es donarà d’alta el client en el registre reserva guardant també la referència del seient reservat.
En el mètode __init__() heu de tenir en compte cridar a l’init de Thread.
Reserves¶
En el fitxer reserves.py cal dissenyar la funció registreReserves():
- reserves.registreReserves(idAvio='Boeing714', nFiles=30, nAgencies=10)¶
Donat la identificació d’un avió idAvio crearà un objecte avió de la classe
Avióamb nFiles de 6 seients. Engegarà nAgencies fils. A cada fil se li passarà com a paràmetres, el nom de l’agència, l”avió i, les peticios de clients que ha d’atendre.Un cop han finalitzat els fils, invocarà la funcióestadisAgències().
- reserves.estadisAgències(avió, agències)¶
Donat un avió (
Avió), i una llista de fils ja executats dek tipusAgència, examina el registre de reserves de places efectuats, fent un comptatge per seient. Si troba que un seient s’ha reservat més d’un cop ho mostra per pantalla. (Amb això controlarem si ha hagut «overbooking»). Compta també el total de reserves dels fils i els compara amb el nombre de places de l’avió per saber si hi ha plena ocupació. També mostra una llista de nombre de reserves fetes per fil. (Amb això podrem controlar si ha hagut inanició d’algun fil). Finalment mostrarà una llista de clients ordenada per data de reserva (Amb això podrem veure com s’ha anat fent sequencialment les reserves concurrentment).
Programa principal¶
A més, cal que reserves.py pugui invocar-se com un programa amb arguments en la línia de bash (No us oblideu del if __main__... per poder proves en l’íntèrpret de python si cal). Per exemple,
python3 reserves.py Boeing417 50 15
Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing417 de 50 files de 6 seients i competiran per omplir-lo 15 agències de viatge.
$ python3 reserves.py Boeing333 50
Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing333.db de 50 files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge.
$ python3 reserves.py Boeing333
Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing333.db de 30 (valor per omissió) files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge.
$ python3 reserves.py
Aquesta línia crearà una base de dades (si aquesta no existeix) Boeing714.db de 30 (valor per omissió) files de 6 seients i competiran per omplir-lo 10 (valor per omissió) agències de viatge.
Si hi han més de tres arguments, mostrarà el missatge:
$ python3 reserves.py 1 2 3 4
Ús: python3 reserves.py [nomAvió] [nombre de files] [nombre d'agències]
Notes¶
A més de l’aportació de la classe Avió, aportem en el fitxer esborrany.py el disseny de la funció estadisAgències().
Generació automàtica de peticions¶
Exemple d’una llista de 10 fils amb generació automàtica de noms d’agències i llista de noms de clients (els noms de clients són números (str), cada agència té prous clients per omplir l’avió):
agencies = [Agència(f'age{i:03d}',
avió,
map(str, range(i*nPlaces , i*nPlaces + nPlaces))) for i in range(10)]
Visualització de les bases de dades d’Avió¶
Disposeu del programa mostra.py per visualitzar el contingut de la base de dades de l’avió. Per exemple, per mostrar el contingut de la base de dades Boeing714.db, feu:
python3 mostra.py Boeing714
Si us equivoqueu de nom us crearà una base de dades buida amb el nom equivocat.
Proves¶
Per les proves, recordeu que la base de dades guarda els resultats produits en anteriors sessions. Per exemple, si en l’avió està tot reservat, les agències no podran reservar res. Cal esborrar la base de dades corresponent.
Protecció de zones crítiques¶
Si no heu posat cap mecanisme de protecció (panys, semàfors) de les zones crítiques en el disseny us sortirà que alguns seients estan reservats més d’un cop, iper tant, no teniu un disseny correcte de l’aplicació. Cal protegir la modificació de les dades de l’avió amb semàfors (threading.Semaphore) o panys (threading.Lock). Heu de vigilar que el mecanisme de protecció a més d’evitar que un seient sigui reservat més d’un cop, no deixi en inanició cap fil-agència. Les lectures o consultes d’un seient de forma concurrent no son cap problema. En canvi la modificació d’un seient lliure per fer una reserva ha de fer-se de forma atòmica.
Un exemple de la comanda (executada sempre sense cap Boeing714.db existent) per eveure els efectes de protecció/no protecció de les zones crítiques:
produeix els següent resultat a pantalla
sense protecció zona crítica:
Exception in thread Thread-8:
Traceback (most recent call last):
File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
self.avio[s] = (p, datetime.datetime.now())
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 77, in __setitem__
self.connexió.commit()
sqlite3.DatabaseError: no more rows available
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
self.avio[s] = (p, datetime.datetime.now())
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 76, in __setitem__
c.execute(self.modifica, (nom, data, seient))
sqlite3.DatabaseError: another row available
Exception in thread Thread-5:
Traceback (most recent call last):
File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
self.avio[s] = (p, datetime.datetime.now())
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 77, in __setitem__
self.connexió.commit()
sqlite3.DatabaseError: no more rows available
Exception in thread Thread-9:
Traceback (most recent call last):
File "/usr/lib/python3.7/threading.py", line 917, in _bootstrap_inner
self.run()
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/agencia.py", line 28, in run
self.avio[s] = (p, datetime.datetime.now())
File "/home/josep/treball/transpas/pdc/portalExercicis/practiques/concurrencia/reservesAvio/avio.py", line 77, in __setitem__
self.connexió.commit()
sqlite3.DatabaseError: no more rows available
Seients amb més d'una reserva:
1B 2
1D 2
1E 2
4E 2
7A 2
55 places OK de 60
Reserves per agència
age000: 9
age001: 0
age002: 11
age003: 12
age004: 1
age005: 10
age006: 10
age007: 0
age008: 1
age009: 11
Clients per ordre temporal de reserva:
seient: client data
1A: 0 2021-04-15 11:35:56.415365
1B: 180 2021-04-15 11:35:56.429113
1C: 300 2021-04-15 11:35:56.438607
1D: 120 2021-04-15 11:35:56.454074
1E: 480 2021-04-15 11:35:56.475228
1F: 301 2021-04-15 11:35:56.485902
2A: 181 2021-04-15 11:35:56.493298
2B: 121 2021-04-15 11:35:56.507818
2C: 541 2021-04-15 11:35:56.518379
2D: 361 2021-04-15 11:35:56.528985
2E: 481 2021-04-15 11:35:56.543518
2F: 182 2021-04-15 11:35:56.558052
3A: 1 2021-04-15 11:35:56.566463
3B: 542 2021-04-15 11:35:56.574433
3C: 122 2021-04-15 11:35:56.582675
3D: 362 2021-04-15 11:35:56.596311
3E: 303 2021-04-15 11:35:56.610778
3F: 183 2021-04-15 11:35:56.619879
4A: 543 2021-04-15 11:35:56.636655
4B: 123 2021-04-15 11:35:56.650997
4C: 363 2021-04-15 11:35:56.661869
4D: 2 2021-04-15 11:35:56.679214
4E: 124 2021-04-15 11:35:56.706305
4F: 544 2021-04-15 11:35:56.717270
5A: 304 2021-04-15 11:35:56.731909
5B: 364 2021-04-15 11:35:56.753018
5C: 185 2021-04-15 11:35:56.763985
5D: 3 2021-04-15 11:35:56.776006
5E: 125 2021-04-15 11:35:56.803137
5F: 305 2021-04-15 11:35:56.816639
6A: 545 2021-04-15 11:35:56.827375
6B: 186 2021-04-15 11:35:56.851167
6C: 365 2021-04-15 11:35:56.860224
6D: 4 2021-04-15 11:35:56.872357
6E: 126 2021-04-15 11:35:56.906578
6F: 306 2021-04-15 11:35:56.933268
7A: 187 2021-04-15 11:35:56.944288
7B: 366 2021-04-15 11:35:56.974656
7C: 5 2021-04-15 11:35:56.984082
7D: 188 2021-04-15 11:35:57.039537
7E: 127 2021-04-15 11:35:57.052910
7F: 547 2021-04-15 11:35:57.071954
8A: 307 2021-04-15 11:35:57.088853
8B: 367 2021-04-15 11:35:57.126609
8C: 6 2021-04-15 11:35:57.136234
8D: 189 2021-04-15 11:35:57.148083
8E: 308 2021-04-15 11:35:57.194276
8F: 548 2021-04-15 11:35:57.225066
9A: 128 2021-04-15 11:35:57.243125
9B: 368 2021-04-15 11:35:57.280971
9C: 7 2021-04-15 11:35:57.291416
9D: 190 2021-04-15 11:35:57.302569
9E: 549 2021-04-15 11:35:57.334949
9F: 309 2021-04-15 11:35:57.357888
10A: 129 2021-04-15 11:35:57.381085
10B: 550 2021-04-15 11:35:57.444012
10C: 369 2021-04-15 11:35:57.454294
10D: 8 2021-04-15 11:35:57.465641
10E: 191 2021-04-15 11:35:57.482016
10F: 130 2021-04-15 11:35:57.521119
amb protecció zona crítica:
Seients amb més d'una reserva:
Places reservades sense "overbooking"
Tot l'avió ocupat
Reserves per agència
age000: 7
age001: 7
age002: 6
age003: 6
age004: 5
age005: 4
age006: 7
age007: 8
age008: 5
age009: 5
Clients per ordre temporal de reserva:
seient: client data
1A: 120 2021-04-15 11:33:56.697482
1B: 121 2021-04-15 11:33:56.703578
1C: 420 2021-04-15 11:33:56.708988
1D: 360 2021-04-15 11:33:56.712665
1E: 421 2021-04-15 11:33:56.717462
1F: 60 2021-04-15 11:33:56.722207
2A: 361 2021-04-15 11:33:56.730978
2B: 422 2021-04-15 11:33:56.737455
2C: 0 2021-04-15 11:33:56.746065
2D: 480 2021-04-15 11:33:56.754503
2E: 240 2021-04-15 11:33:56.763325
2F: 362 2021-04-15 11:33:56.772483
3A: 423 2021-04-15 11:33:56.780114
3B: 61 2021-04-15 11:33:56.788100
3C: 1 2021-04-15 11:33:56.795386
3D: 241 2021-04-15 11:33:56.803574
3E: 122 2021-04-15 11:33:56.811042
3F: 180 2021-04-15 11:33:56.818524
4A: 300 2021-04-15 11:33:56.832600
4B: 540 2021-04-15 11:33:56.842167
4C: 62 2021-04-15 11:33:56.851745
4D: 424 2021-04-15 11:33:56.860037
4E: 2 2021-04-15 11:33:56.873273
4F: 363 2021-04-15 11:33:56.880307
5A: 242 2021-04-15 11:33:56.889080
5B: 181 2021-04-15 11:33:56.901334
5C: 481 2021-04-15 11:33:56.910263
5D: 63 2021-04-15 11:33:56.918640
5E: 301 2021-04-15 11:33:56.926129
5F: 541 2021-04-15 11:33:56.943315
6A: 425 2021-04-15 11:33:56.955006
6B: 123 2021-04-15 11:33:56.963479
6C: 3 2021-04-15 11:33:56.970202
6D: 364 2021-04-15 11:33:56.989061
6E: 182 2021-04-15 11:33:57.006979
6F: 482 2021-04-15 11:33:57.018385
7A: 64 2021-04-15 11:33:57.034885
7B: 243 2021-04-15 11:33:57.049167
7C: 302 2021-04-15 11:33:57.069741
7D: 4 2021-04-15 11:33:57.084110
7E: 183 2021-04-15 11:33:57.090392
7F: 542 2021-04-15 11:33:57.106806
8A: 124 2021-04-15 11:33:57.135982
8B: 426 2021-04-15 11:33:57.143282
8C: 483 2021-04-15 11:33:57.151060
8D: 65 2021-04-15 11:33:57.167389
8E: 365 2021-04-15 11:33:57.182145
8F: 5 2021-04-15 11:33:57.198881
9A: 184 2021-04-15 11:33:57.221536
9B: 303 2021-04-15 11:33:57.237455
9C: 543 2021-04-15 11:33:57.245070
9D: 244 2021-04-15 11:33:57.259423
9E: 125 2021-04-15 11:33:57.282479
9F: 366 2021-04-15 11:33:57.290265
10A: 427 2021-04-15 11:33:57.304745
10B: 66 2021-04-15 11:33:57.322370
10C: 6 2021-04-15 11:33:57.350723
10D: 544 2021-04-15 11:33:57.362959
10E: 185 2021-04-15 11:33:57.377844
10F: 484 2021-04-15 11:33:57.391248