-
- Fordeler med å implementere tråder i brukerområdet/user space:
Den største fordelen med å implementere tråder i brukerområdet er at det er mer effektivt og raskere enn å
implementere dem i kjernen. Dette skyldes at det å bytte mellom tråder i brukerområdet ikke krever noen systemkall
eller kontekstbytte til kjernen, som kan være dyrt i form av tid og ressurser. I tillegg kan trådplanlegging i
brukerområdet tilpasses spesifikt til en applikasjons behov, noe som kan bidra til bedre ytelse.
-
Ulemper med å implementere tråder i brukerområdet:
Den største ulempen med å implementere tråder i brukerområdet er at de ikke kan dra nytte av flerkjerneprosessorer på
samme måte som kjernetråder kan. Hvis en tråd i brukerområdet blokkeres (for eksempel på grunn av I/O-operasjoner),
vil hele prosessen bli blokkert, selv om det er andre tråder i prosessen som er klare til å kjøre. Dette skyldes at
operativsystemet, som håndterer planleggingen på lavt nivå, bare ser prosessen som en enkelt enhet og er ikke klar
over de individuelle trådene innenfor den.
-
Linux-JVM i samarbeid med Linux-kjernen tar ikke hensyn til prioriteten, det samme skjer om alle prioritetene er like. Om man som root kjører java med opsjonen -XX:ThreadPriorityPolicy=1 vil det settes nice-verdier fra -5 til 4 på trådene.
(men ikke til negative verdier på docker-containeren, se over; her er det ikke forskjell på root og vanlig bruker,
begge kan kun sette prioriteten ned) Kun root kan sette opp prioriteten. Men dette kunne vært gjort slik at prioritetene ble mappet til niceverdi 0-9.
-
Windows-JVM i samarbeid med NT-kjernen tar ihensyn til prioriteten, det er stor forskjell på prioritet 4 og 5.
- (Oblig)
// Kompileres med javac NosynchThread.java // Run: java NosynchThread
import java.lang.Thread;
class SaldoThread extends Thread { static int MAX = 50000000; static int count = 0; public static int saldo; // Felles variable, gir race condition int id; SaldoThread() { count++; id = count; } public void run() { System.out.println("Thread nr. "+ id +", med prioritet " + getPriority() + " starter"); updateSaldo(); } public void updateSaldo() { int i; if(id == 1) { for(i = 1;i < MAX;i++) { saldo++; } } else { for(i = 1;i < MAX;i++) { saldo--; } } System.out.println("Thread nr. " + id + " ferdig. Saldo: " + saldo); } }
class NosynchThread extends Thread { public static void main (String args[]) { int i; System.out.println("Starter to threads!");
SaldoThread s1 = new SaldoThread(); SaldoThread s2 = new SaldoThread(); s1.start(); s2.start();
try{s1.join();} catch (InterruptedException e){} try{s2.join();} catch (InterruptedException e){}
System.out.println("Endelig total saldo: " +SaldoThread.saldo); } }
|
Når trådene kjøres med native-threads vil de startes som to uavhengige Linux-threads,
schedules av Linux-kjernen og vil nå kjøre samtidig, typisk på hver sin CPU, når du kjører på et system
med flere CPUer. Om de kjører på samme CPU, vil en context switch midt i saldo-oppdateringen være like ødeleggende som om to CPUer laster ned samme saldo og så legger resultatet tilbake uten å synkronisere.
Dette er fordi en saldo-oppdatering består av flere
JVM-instruksjoner som man kan se med $ javap -c SaldoThread
17: getstatic #17 // Field saldo:I
20: iconst_1
21: iadd
22: putstatic #17 // Field saldo:I
I en context switch lagres registerverdiene og dette fører til
at oppdateringer på den felles variabelen saldo kan forsvinne når den andre
tråden overtar. Resultatene vil derfor variere for hver kjøring og kan avvike fra null med flere hundre tusen.
Med taskset vil alle trådene tvinges til å kjøre på samme CPU, men context switcher vil likevel kunne ødelegge. Når trådene kjører på samme CPU vil de kjøre en av gangen, slik at ingen av operasjonene utføres reellt sett samtidig. Så en "kollisjon" kan bare skje hvis det kommer en context switch midt i operasjonen som øker eller minker saldo med en. Og hvis programmet kjører kun ett sekund, skjer det kanskje bare 10 context switcher, så dette vil ikke skje hver gang. Når trådene derimot kjører på hver sin CPU, vil en tråd 10 millioner ganger kunne lese/skrive til variabelen saldo i RAM reellt sett helt samtidig som den andre tråden på en annen CPU gjør det samme. Og det vil da være 10 millioner muligheter for kollisjoner hvor de to trådene overskriver hverandre.
-
Race condition: Når to eller flere prosesser skriver/leser til en eller annen form for delte data og sluttresultatet
er avhengig av hvilket tidspunkt prosessene kjører. Minst en av prosessene må skrive.
- (Oblig)
Men minimal.s blir alltid resultatet det samme fordi variabelen svar blir endret med en enkelt instruksjon og når trådene blir tvunget til å kjøre på samme CPU med taskset, vil ikke en context switch kunne ødelegge. Uten takset blir resultatet forskjellig hver gang fordi trådene da kjører på hver sin CPU og henter data fra RAM uavhengig av hverandre.
Når man kompilerer med en.c istedet, lager kompilatoren flere maskinkodelinjer som oppdaterer svar og det kan man se av en.s:
movl svar(%rip), %eax
addl $1, %eax
movl %eax, svar(%rip)
Dermed vil en sjelden gang en context switch inne i denne koden ødelegge resultatet. Når man bruker opsjonen -O til gcc, vil koden optimaliseres for hastighet og da vil oppdateringen kun bestå av en linje:
addl $1, svar(%rip)
Dermed vil det blir riktig resultat hver gang på samme måte som når vi brukte minimal.s.
main(){ int i,S = 0; for(i=1;i < 5;i++){ S = S + i; } }
|
-0x4(%rbp) tilsvarer løkke-variabelen i og -0x8(%rbp) tilsvarer summen S.
Variabelen i starter
lik 1 og den legges til S for hver runde og økes deretter med 1. Så lenge i er mindre eller lik 4 (jle), hoppes det
tilbake til start i koden og dermed kjøres løkken 4 ganger som den skal og i får verdiene 1,2,3 og 4.
Når den er ferdig ligger dermed tallet 10 i -0x8(%rbp).
- (Oblig)
class For{ public static void main(String args[]){ int i,S = 0; for(i=1;i < 5;i++){ S = S + i; } } }
|
- (Oblig)
Gjeste-OS kjører i user mode og vil ikke fungere som på vanlig hardware om en slik instruksjon droppes. Det kan føre til at OS crasher i verste fall. Disse sensitive instruksjonene ble endret slik at de trap'er til kernel mode når de utføres i user mode, istedet for å ikke gjøre noe.
- (Oblig)
- (Oblig)
Scriptet kan kjøres som det er.
Function:mkdir
Alias cd = Set-Location
Alias echo = Write-Output
Alias mv = Mov-Item
Alias cp = Copy-Item
Alias rm = Remove-Item
- (Oblig)
PS C:\> $PSVersionTable
- (Oblig)
PS C:\> (Get-Childitem fil.txt).CreationTime
- (Oblig)
PS C:\> (ls C:\Windows\system.ini).CreationTime.Year
-
PS C:\> (ls fil.txt).FullName
- (Oblig)
PS C:\> (ps idle).id
- (Oblig)
PS C:\> (ps notepad).Kill()
PS C:\> kill (ps idle).id
Begge blir drept hvis det er to notepad-prosesser.
-
Den første måten henter ut størrelsen, direkte fra feltet "Length", mens den andre måten kaller
metoden get_Length().
- (Oblig)
for($i=2; $i -lt 10; $i += 2){
echo "Linje $i"
}
Alternativt
for($i=1; $i -lt 5; $i++){echo "Linje $(2*$i)"}
eller med foreach som også er en slags løkke
foreach ($t in 2,4,6,8){"Linje $t"}
-
ps | foreach {if($_.name -eq "notepad") {kill $_.Id}}
En enda enklere (og bedre) løsning er følgende
kill -Name notepad
- (Oblig)