Synchronizace vláken - problém kritické sekce, zámky, semafory
Jan 15, 20253 min read
Synchronizace vláken je klíčový aspekt programování v prostředí, kde více vláken přistupuje ke sdíleným zdrojům.
Bez správné synchronizace mohou nastat problémy, jako je nekonzistence dat a chyba souběhu (race conditions).
Pro řešení těchto problémů se používají různé synchronizační mechanismy, jako jsou kritické sekce, zámky (locks) a semafory.
Problém kritické sekce
Kritická sekce je část kódu, kde přístup k sdílenému zdroji musí být omezen tak, aby k němu v daném čase mohlo přistupovat pouze jedno vlákno.
Pokud více vláken vstoupí do kritické sekce současně, může dojít k nekonzistenci dat.
Požadavky na kritickou sekci
vzájemné vyloučení - nejvýše jeden proces v kritické sekci
absence zbytečného čekání - není-li žádný proces v kritické sekci a proces do ní chce vstoupit, není mu bráněno
zaručený vstup - pokud se proces snaží vstoupit do kritické sekce, jednou musí uspět
Řešení problému kritické sekce
Řešení problému kritické sekce spočívá v zajištění toho, že pouze jedno vlákno má přístup ke sdílenému zdroji nebo kritické sekci v daném čase.
V Pythonu lze tento problém řešit pomocí synchronizačních mechanismů, jako jsou zámky (locks) a semafory.
Správným použitím těchto synchronizačních mechanismů lze zajistit, že kritická sekce bude bezpečně chráněna před souběžným přístupem více vláken, čímž se předejde nekonzistenci dat a dalším problémům spojeným s paralelním zpracováním.
Zámky
Zámky jsou synchronizační mechanismy, které umožňují exkluzivní přístup k určitému zdroji nebo části kódu.
Používají se k minimalizaci problémů kritické sekce a zabraňují soutěžení mezi vlákny o sdílené zdroje.
Zámky mohou být buď jednoduché (binární), které mají dvě stavy (otevřený/zavřený), nebo mohou být složitější (například zámky s podporou priorit).
Python poskytuje zámky prostřednictvím modulu threading.
příklad použití zámku
import threadingshared_resource = 0lock = threading.Lock()def increment(): global shared_resource for _ in range(100000): lock.acquire() # Získání zámku shared_resource += 1 lock.release() # Uvolnění zámkuthread1 = threading.Thread(target=increment)thread2 = threading.Thread(target=increment)thread1.start()thread2.start()thread1.join()thread2.join()print(shared_resource)
V tomto příkladu zámek lock zajišťuje, že pouze jedno vlákno může získat exkluzivní přístup k inkrementaci sdílené proměnné shared_resource v každém okamžiku.
alternativní použití zámku
def increment(): global shared_resource for _ in range(100000): with lock: # získání zámku, kritická sekce v shared_resource += 1 # indentaci a automatické uvolnění
Semafor
Semafor je synchronizační abstrakce, která udržuje interní počítadlo a umožňuje omezit počet vláken, která mohou současně získat přístup k sdíleným prostředkům.
Semafor může mít počáteční hodnotu větší nebo rovnu nule.
Pokud je počet vláken, která žádají o zámek, větší než hodnota semaforu, vlákna jsou umístěna do fronty čekajících a jsou probuzena, jakmile je semafor uvolněn.
příklad použití zámku
import threadingimport timesemaphore = threading.Semaphore(5) # Vytvoření semaforu s maximálním # počtem 5 povolených vlákendef worker(): with semaphore: print("Vlákno", threading.current_thread().name, "získalo povolení") # Simulace dlouhodobého úkolu time.sleep(2) print("Vlákno", threading.current_thread().name, "končí")for i in range(10): t = threading.Thread(target=worker) # Spuštění vlákna s funkcí worker t.start()
opět místo with semaphore můžeme použít funkce semaphore.acquire() a semaphore.release()