Avsnitt fra Tanenbaum: 2.2 og 2.3 |
Uredigert opptak av hele første time av forelesningen ( 00:46:03)
Uredigert opptak av hele andre time av forelesningen ( 00:46:37)
Opptak av forelesningen inndelt etter temaer:
os12del1.mp4
(02:08) Intro om planene fremover og om PowerShell
os12del2.mp4
(03:20) Om ukens oppgaver og dagens temaer
os12del3.mp4
(01:59) Spørsmål: Er uke 14 obligatorisk? Nei, oppgver merket (oblig) fra nå er de viktigste å få med seg.
os12del4.mp4
(01:20) Innledning om dagens temaer
os12del5.mp4
(08:07) Slides: Kritisk avsnitt, disableInterupts, Mutex
os12del6.mp4
(03:22) Slides: Linux og Windows eksempel
os12del7.mp4
(10:32) Slides: Softwareløsning av MUTEX, GetMutex(lock), forsøk 1
os12del8.mp4
(03:24) Slides: Hardware-støttet mutex og X86-instruksjonen lock
os12del9.mp4
(00:23) Spørsmål: Virker testAndSet også for flere CPU-er? -Ja
os12del10.mp4
(06:45) Demo: lock og pthreads, oppsummering av demo fra forrige uke, Men hva med flere linjer og samme CPU?
os12del11.mp4
(09:36) Demo: lock og pthreads (hvorfor lengre tid på en CPU?) Samme CPU og tre linjer for å øke svar
os12del12.mp4
(06:37) Tegning og forklaring av 2 CPU-er og låsing av minnebus med lock
os12del13.mp4
(05:50) Tegning og forklaring av en CPU og taskset slik at begge tråder kjører på samme CPU
os12del14.mp4
(01:14) Demo: Oppsummering av lock og pthreads med en og tre instruksjoner
os12del15.mp4
(05:39) Slides: semaforer og mutex, implementasjon av semafor i OS
NB! Slides: Moitorer og Java synkronisering er ikke klippet ut og er vist fra 38:00 i andre time:
Uredigert opptak av hele andre time av forelesningen ( 00:46:37)
os13del3.mp4
(07:16) Slides: Deadlock
os13del4.mp4
(08:34) Slides: Dining Philosophers Problem og mulige løsninger på deadlock
A | Skru av scheduler før kritisk avsnitt. P1 kode:
| |
B | Bruke en form for lås som gjør at bare en prosess av gangen har tilgang til felles data.
|
/var/mail/haugerud.lock |
GetMutex(lock)
og ReleaseMutex(lock)
som gjør at en prosess av gangen kan sette en lock. Da kan problemet løses med:
GetMutex(lock); // henter nøkkel KritiskAvsnitt(); // saldo -= mill; ReleaseMutex(lock); // gir fra seg nøkkel |
static boolean lock = false; // felles variabel GetMutex(lock) { while(lock){} // venter til lock blir false lock = true; } ReleaseMutex(lock) { lock = false; } |
while(lock){}
når P1 kjører?
Da rekker ikke P1 å sette lock til true og P2 kunne gå inn i kritisk avsnitt, switches ut og P1 kan gå inn i kritisk avsnitt samtidig! Altså er ikke denne metoden korrekt. Dette er vanskelig å løse med en algoritme, som forsøkene i ukens oppgaver viser. Men Peterson-algoritmen gir en elegant løsning.
GetMutex(lock) { while(testAndSet(lock)) {} } |
lock
som vi så på i forrige uke og som hindret race condition når kritisk avsnitt kun består av en enkelt instruksjon . Den vil for neste instruksjon som utføres låse
minnebussen slik at instruksjoner på andre CPUer ikke samtidig kan hente eller lagre noe i RAM. Dette sikrer at instruksjonen etter lock som utføres på en variabel i minne får avsluttet hele
sin operasjon uten at RAM endres. Dermed avverges problemet ved at det kritiske avsnittet fullføres før noen andre tråder slipper til.
Signal(S): S = S + 1; # Kalles ofte Up(), V() Wait(S): while(S <= 0) {}; S = S - 1; # Kalles ofte Down(), P() |
Wait(S); KritiskAvsnitt(); Signal(S); |
Signal(S){ S = S + 1; if(S <= 0){ wakeup(prosess); # Sett igang neste prosess fra venteliste } } |
Wait(S){ S = S - 1; if(S < 0){ block(prosess); # Legg prosess i venteliste } } |
En flash-demo det samme kan sees på denne web-siden.
PA | PB-kode |
---|---|
A1 | B1 |
A2 | Wait(S) |
A3 | K1 |
Wait(S) | K2 |
K1 | K3 |
K2 | Signal(S) |
K3 | B2 |
Signal(S) | B3 |
A4 | B4 |
A | B | S |
---|---|---|
B1 | 1 | |
Wait(S) | 0 | |
K1 | 0 | |
A1 |
![]() |
0 |
A2 | 0 | |
A3 | 0 | |
Wait(S) | OS legger A i kø | -1 |
![]() |
K2 | -1 |
K3 | -1 | |
Signal(S) | 0 (A ut av kø) | |
B2 | 0 | |
B3 | 0 | |
B4 | 0 | |
K1 |
![]() |
0 |
K2 | 0 | |
K3 | 0 | |
Signal(S) | 1 | |
A4 | 1 |
PA | PB-kode |
---|---|
A1 | B1 |
A2 | Wait(S) |
A3 | B2 |
Signal(S) | B3 |
A4 | B4 |
A | B | S |
---|---|---|
A1 | 0 | |
A2 | 0 | |
![]() |
B1 | 0 |
Wait(S) | -1 | |
A3 |
![]() |
-1 |
Signal(S) | 0 | |
A4 | 0 | |
![]() |
B2 | 0 |
B3 | 0 |
A | B | S |
---|---|---|
A1 | 0 | |
A2 | 0 | |
A3 | 0 | |
Signal(S) | 1 | |
A4 | 1 | |
![]() |
B1 | 1 |
Wait(S) | 0 | |
B2 | 0 | |
B3 | 0 |
atomic_inc_and_test()
Dette er implementert i Java som har et eget statement synchronized
for å synkronisere bruken av
felles variabler. Alle java-objekter har en egen monitor-lås, tilsvarende variabelen lock vi har brukt i tidligere eksempler.
Når man bruker statementet synchronized()
må man derfor knytte det opp mot ett objekt og dermed bruke
dette objektets lås. En integer verdi som variabelen saldo
er ikke et objekt, i motsetning til for eksempel et array, og derfor lager vi et objekt som vi kaller lock
for så å knytte synchronized()
opp mot dette objektet:
public static int saldo; // Felles variable, gir race condition public static Object lock = new Object(); // Argumentet til synchronized må være et objekt |
Dermed kan man gjøre synchronized(lock)
rundt en kodeblokk
synchronized(lock) { saldo++; } |
I praksis betyr det at om en tråd kjører instruksjoner inne i denne kodeblokken, vil andre tråder settes på vent om de prøver å kjøre det samme kodeavsnittet. Dette avsnittet er da et kritisk avsnitt. Det tilsvarer helt å kalle wait før og signal etter et kritisk avsnitt. Gjør man det med eksempelet fra forrige forelesning, tar beregningene lenger tid, men saldo blir 0 til slutt.
Ser man på java bytekoden, kan man se at koden som oppdaterer saldo da blir beskyttet i en monitor:
$ javap -private -c SaldoThread 17: getstatic #17 // Field lock:Ljava/lang/Object; 20: dup 21: astore_2 22: monitorenter 23: getstatic #18 // Field saldo:I 26: iconst_1 27: iadd 28: putstatic #18 // Field saldo:I |
Man kan også definere hele metoder som synchronized. I Figure 2-35 i læreboka defineres i et eksempel metoden
public synchronized void insert(int val) |
Dette forsikrer programmereren om at kun en tråd av gangen kan kjøre denne metoden.
En løsning på vårt problem kunne være å gjøre følgende:
private static synchronized void upSaldo() { saldo++; } private static synchronized void downSaldo() { saldo--; } |
Filosoftilstander:
Eks. 2: Deadlock med to semaforer S1 og S2, initialisert til 1:
PA | PB-kode |
---|---|
Wait(S1) | |
Wait(S2) | |
Wait(S2) | |
Wait(S1) | |
. | . |
. | . |
. | . |
Signal(S1) | Signal(S2) |
Signal(S2) | Signal(S1) |
import multiprocessing, gir en løsning som lar deg opprette prosesser, som hver har sin egen Python-interpreter og minneplass. Dette omgår GIL og lar deg utnytte flere CPU-kjerner.
Noen biblioteker som NumPy og Pandas er implementert i C og frigjør GIL under tunge beregninger. Dette kan tillate parallell utførelse av beregninger uten å være begrenset av GIL.
Hvis man kjører følgende kode på en server med flere CPUer, vil kun en av trådene kjøre av gangen:
import threading class SaldoThread(threading.Thread): MAX = 200000000 count = 0 saldo = 0 # Felles variabel, gir race condition? def __init__(self): super().__init__() SaldoThread.count += 1 self.id = SaldoThread.count def run(self): print(f"Tråd nr. {self.id} starter") self.update_saldo() def update_saldo(self): if self.id == 1: for _ in range(SaldoThread.MAX): SaldoThread.saldo += 1 else: for _ in range(SaldoThread.MAX): SaldoThread.saldo -= 1 print(f"Tråd nr. {self.id} ferdig. Saldo: {SaldoThread.saldo}") class NosynchThread: @staticmethod def main(): print("Starter to tråder!") s1 = SaldoThread() s2 = SaldoThread() s1.start() s2.start() s1.join() s2.join() print(f"Endelig total saldo: {SaldoThread.saldo}") if __name__ == "__main__": NosynchThread.main() |