Threadlocal storage November 17, 2008 22:19 about 1 year ago
Jeg er midt i en debugsession i en Java applikation som nægter at virke. Maskinen er en 64bit server med 16 fysiske CPU’er. Efter et par timer går det op for mig at Java’s threadlocal er årsagen. Applikationen køre fint på en dual core maskine hvor CPU’er har fælles hukommelse men kan ikke køre på en maskine hvor alle CPU’er har hver deres hukommelse. Jeg går ud fra at det underliggende operativsystem skifter CPU hvorefter min Java proces mister sin kontekst.
ThreadLocal er en slags global variable allokeret til en tråd på en måde så den er synlig for alle objekter der eksekveres i denne i denne tråd. Denne metode benyttes ofte til at binde fx bruger-id eller transaction-id til en tråd og i meget sjældent tilfælde en database forbindelse. I dette tilfælde er det det sidste.
Hvis man kigger ind i threadlocal klassen finder man en nøgle med værdien 0×61c88647 og alle nye objekter af denne type tildeles værdien hash_increment plus værdien 0×61c88647. Altså, værdien 0×61c88647 er jo et tal i 16tals formatet og hvis man omsætter det til et decimaltal får man enten 2654435769 eller -1640531527. Det er en 32-bit signed version af den usignerede 2654435769 og er den faktiske værdi som Java bruger til at beregne hash.
Tallet repræsenterer ”The Golden Section” eller den gyldne ratio(sqrt (5) -1) gange to til potensen af 31. Check Dr Ron Knott algoritmer og Donald Knuth, de bøger hører hjemme hos en enhver seriøs programmør. Måske også Merck Manual ”adorns your health practitioner’s”.
Koden for threadlocal en smule kompliceret og jeg må ty til Dr Heinz for en forklaring. Tanken bag tidligere versioner af Java threadlocals var at dele state mellem flere tråde hvilket gør dem praktisk talt ubrugelige for multi-core-applikationer. I Java 1.4, blev et nyt design introduceret hvori threadlocals blev opbevaret direkte i den eksekverede tråd. Når man kalder metoden get() sker det på tråden og der returneres en instans af threadlocalmap som er en slags indre klasse af threadlocal. I teorien skulle en udgående tråd fjerne sine threadlocal værdier lige før garbage collection kaldes med exit() metoden. Hvis vi derimod glemme at fjerne en threadlocal er den stadig berettiget til automatisk garbage collection. Det skyldes en weak references intern i threadlocal til threadlocalmap der har normalt stærke henvisninger til værdier.
Et threadlocal objekt bliver garbage collected når den eksekverede tråd dør. Undtagen hvis tråden er blevet trukket ud af en objekt pool som fx i nogle applikationsserverer, i disse tilfælde bliver værdierne aldrig garbage collected.
Når et threadlocal objekt bliver garbage collected bliver den svage reference threadlocalmap ryddet. Hvornår bliver det så virkeligt slettet fra threadlocalmap. Når vi kalder get() metoden på en indgang i java.util.WeakHashMap fjernes alle gamle indgange. Hvis vi kalder get() på threadlocalmap går det hurtigere men kan give forældet poster og dermed memory leaks.
Når man kalder ThreadLocal set() kan det falde ind under disse kategorier:
- hvis værdi ikke findes indsættes den.
- hvis vi finder vores nøgle, byttes den med ny værdi.
- hvis der ikke er plads til flere værdier. Denne fase bruger en O(log2n) algoritmen i første omgang.
- hvis en nøglen er fjernet så er posten slettet.
Hvis du ønsker flere oplysninger om, hvordan det virker, kan du få nogle ideer fra Knuth 6.4 AlgoritmeR.
Best Practices hvis du absolut vil bruge threadlocals skal du sørge for at fjerne de indsatte værdier når du er færdig med dem, og helst inden du afleverer din tråd til en tråd pool. Den bedste måde at gøre dette på er at bruge metoden remove() i stedet for set(null) som vil medføre at weak reference mappe bliver fjernes straks sammen med værdien.
By Frank Vilhelmsen - 2 tags: java multicore - 1 comment on Threadlocal storage - Add comment
Stig Hornuff January 12, 2009 07:34 about 1 year ago
The Stig, CPU and more boxes
Den eneste platform jeg pt. kender, der splitter RAM’en op pr. CPU er SUN, men burde ikke give det problem, du beskriver, da den enkelte process ikke har kontrol over hvor (=hvilket CPU-board), der benyttes for allokering.
F.ex så er det normalt på SUN (på de store æsker: 15K, 25K, 6800/6900) at fence CPU’er til netværk og I/O men samtidigt at splitte kernel memory ud på alle boards – og det giver jo alt andet lige nogle krydsreferencer.
Det du skriver lyder MEGET som om Threadlocal Storage definere sig i forhold til CPU’en den kører på – og det kan gøres på 2 måder: CPU eller CPU-core. Lige meget hvilken af de 2 måder, den benytter, så er det noget ret dumt at gøre (siger vist sig selv i et SMP-miljø). Der er Ingen UNIX O/S’er der umiddelbart kræver CPU-awareness, da det vil låse systemet fast i mht. performance på ret mange måder og give en meget skæv og ufleksible schedulering. Så hvis Threadlocal Storage er afhængig af CPU’en, har nogen vist lavet en brøler … ;)
- og jo: Java kan sagtens skalere fint på ret mange CPU’er, hvis applikationen vel at mærke er lavet threadsafe på en ordentlig og gedigen måde. Men det er jo reelt ikke noget nyt, vel? ;)
Hvis threadlocal virkelig checker CPU, så trækker den faktisk hele grundlaget for java væk, da grund-ideen er platform-uafhængighed?
En almindeligt context-switch burde stadig ikke kunne påvirke threadlocal på den beskrevne måde, da du potentielt vil have dette problem 1000-vis af gange pr. sekund (har set context-switch ske 50.000+ gange pr. sekund på SUN – og det er dælme mange). Og der er absolut INGEN garanti for, at ramme samme CPU, da den strengt taget bare napper den første ledige CPU, hvilket faktisk sjældent er den samme.
Den eneste undtagelse er system-mæssig fencing af CPU’’er til givne I/O-operationer og det kan ikke lade sig gøre på user-app niveau – kun på kernel niveau (så vidt jeg har set i hvert fald). Og fencing går normalt på et board, så selv dér kan man ikke garantere hvilken CPU, processen kører på med mindre der kun er een aktiv CPU (dvs. alle andre er
blacklistet).
Ja, altså – med fare for at bevæge mig ind i et relativt religiøst område, så er der formentlig færre muligheder i Windows for at søge information om hvilken CPU man kører på – men det er dog kun et gæt.
Men hvis det er sandt, så giver Windows faktisk et bedre abstraktionslag for java end SUN gør. Men dertil er jo ret vigtigt at vide, om det er Microsofts java, der bruges på Windows eller om det er SUN’s – og endvidere om den kører med native threads (der er i hvert fald forskellige thread libs på SUN mht. java.).