webForumDet fria alternativet

Vad är trådsäkert?

.NET

12 svar · 1 916 visningar · startad av freguz

Medlem sedan feb. 2005280 inlägg
Frågan#1

Lite förbryllad av detta med trådsäkerhet...

scenario: en vanlig C# .net web app med db

Ofta (typ nästan alltid) har jag en Util klass som innehåller massa statiska hjälp metoder, som lever helt inom sig själva, klassen har inga egna variabler.

Är detta trådsäkert?

Enligt de diskussioner jag läst på nätet så säger vissa att det är OK så länge man inte refererar till något utanför själva metoden, andra säger att trådar kan bråka inuti medtoden med varandra.... :OO

Är det bättre och trådsäkert att ha Util som en vanlig klass, som man skapar med Util u = new Util(); osv?

Inte så insatt i detta och skulle behöva lite best practise hjälp :bire

/Freguz

Medlem sedan juni 20008 205 inlägg
#2

Finns lite exempel och förklaring här: http://www.webforum.nu/showthread.php?t=154893

Statiska metoder och instansmetoder följer precis samma regler. Har du en klass i stil med denna:

class C 
{
    private static int x = 0;
    public static int A()
    {
        x++; 
        return x * x; 
    }
    public static int B(int x, int n)
    { 
        for (int ret = 1; n-- > 0; ret *= x)
            ; 
        return ret; 
    }
}

A är inte trådsäker, eftersom den använder ett externt tillstånd i form av x, medan B är trådsäker, eftersom den bara arbetar med parametrar och lokala variabler.

Medlem sedan feb. 2005280 inlägg
#3

Okej tack, då vet jag det :bire

Hur är det med en vanlig klass som man instansierar med Util U = new Util(); och som _inte_ innehåller statiska metoder?

Medlem sedan juni 20008 205 inlägg
#4

Det går inte att säga några generella saker. Om kod inte använder några tillstånd som används av andra trådar är den trådsäker. Om din Util-instans bara använder sina egna medlemsvariabler och du inte skickar iväg referensen till någon annan tråd är du safe. Annars beror det på.

Medlem sedan maj 20012 812 inlägg
#5

frequz skrev:

Enligt de diskussioner jag läst på nätet så säger vissa att det är OK så länge man inte refererar till något utanför själva metoden, andra säger att trådar kan bråka inuti medtoden med varandra....

Eftersom de variabler som du har i din metod kommer att skapas i trådens minnesrymd, så är metoden trådsäker så länge du håller dig inom metoden. Din metod kan ju dock börja kalla på andra metoder i andra klasser som i sig själv inte är trådsäkra och då kan det uppstå problem.

Så säg att du bygger en komponent som är trådsäker, men att du sedan i din komponent kallar på andra komponenter som inte är trådsäkrar, då kommer inte din komponent heller vara trådsäker.

frequz skrev:

Är det bättre och trådsäkert att ha Util som en vanlig klass, som man skapar med Util u = new Util(); osv?

Nej så generellt kan man inte säga att det är, för din klass Util kan ju skapa trådar isig och sedan försöker dessa trådar accessa variabler i denna klass och då kommer inte klassen vara trådsäker bara för att du gör en ny instans av den, utan du måste låta klassen hantera trådsäkertheten själv.

Men om du skapar en ny instans och inte skapar några nya trådar i klassen, så är den trådsäker, eftersom varje tråd kommer hålla ett eget objekt av klassen i sin minnesarea och kan inte påverka variabler som andra trådar påverkar.

- M

Medlem sedan feb. 2005280 inlägg
#6

Tack för det Gladh, nu är jag nog på det klara med hur jag bör koda.

Dvs
1. Statiska metoder är ok, men endast självgående sådana, som inte använder varaibler utifrån eller ropar på andra metoder.

2. Det är lugnt med "vanliga" instansierade objekt, bara de inte skapar nya trådar (och det gör de väldigt sällan), och, om statiska metoder måste anropas, bara anropar självförsörjande sådana.

Alltså jag kodar mestadels enklare standard grejor, klasser för att hålla ordning på produkter, ordrar, kunder, osv, ingen raket science alls, så det borde inte vara några problem att hålla sig till ovanstående ramar. :birp

Medlem sedan jan. 20013 406 inlägg
#7

Hej

Jag har ett komplement till detta börjar med att presentera lite kod:

using System;
using System.Collections.Generic;
using System.Text;

namespace CarApplication
{
   
        
    class Car
    {
        private string
            name,
            color;
        private List<string>
            attributes;

        public string Color
        {
            get { return color; }
            set { color = value; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        [b]public List<string> Attributes
        {
            get { return attributes; }
            set { attributes = value; }
        }[/b]

        public Car()
        {
            [b]Attributes = new List<string>();[/b]
        }

        [b]public static List<Car> getAll()[/b]
        {
            //Kod för att hämta ur databas
            List<Car> objColl = new List<Car>();
            while (dr.read())
            {
                Car objThis = new Car();
                objThis.Name = dr["name"].ToString();
                objThis.Color = dr["color"].ToString();
                objThis.Attributes.Add(dr["wheels"].ToString());
                objThis.Attributes.Add(dr["motor"].ToString());
                objColl.Add(objThis);
            }
            return objColl;
        }

    }
}

GetAll är ju en statisk funktion som returnerar en lista av Car objekt.
Är detta trådsäkert? Tänker främst på att objekten i Car har även den en Lista med strängar som initieras i konstruktorn.

Är detta dålig design?

Anroppar sedan på detta sätt:

List<Car> carList = Car.GetAll();
Medlem sedan maj 20012 812 inlägg
#8

Addeladde skrev:

Är detta trådsäkert?

Som jag kan se det så skulle detta vara trådsäkert eftersom private List<string> attributes inte kommer att delas mellan några trådar.

Du skapar en klass car i din static-metod, det betyder att denna klassen kommer få en egen minnesrymd för varje tråd som accessar metoden. Så jag kan inte se att du skulle få några problem med trådar i denna kod. I ditt fall så har din static metod egentligen inget med din Carklass att göra mer än att du lagt din GetAll() metod där, du skulle lika gärna kunnat skapa en CarManager klass och där lagt din static klass så du skrivit CarManager.GetAll() istället för Car.GetAll(). Det finns en del som tycker att det hade varit bättre designmässigt att göra så, eftersom en bil inte kan skapa sig själv utan den behöver ju byggas av någon.

Om din List<> attribute istället hade varit static, så skulle du kunna få problem med trådhanteringen eftersom flera trådar då hade kunnat accessa samma minnesarea.

- M

Medlem sedan jan. 20013 406 inlägg
#9

Hej

Om Car skapar Wheels som också är en klass och har en statisk metod som genererar flera Wheels. Se exempel nedan:

using System;
using System.Collections.Generic;
using System.Text;

namespace CarApplication
{
   
        
    class Car
    {
        private string
            name,
            color;
        private List<Wheel>
            wheels;

        public string Color
        {
            get { return color; }
            set { color = value; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        [B]public List<Wheel> Wheels
        {
            get { return wheels; }
            set { wheels= value; }
        }[/B]
        public Car()
        {
            Wheels = new List<Wheel>();
        }

        public static List<Car> getAll()
        {
            //Kod för att hämta ur databas
            List<Car> objColl = new List<Car>();
            while (dr.read())
            {
                Car objThis = new Car();
                objThis.Name = dr["name"].ToString();
                objThis.Color = dr["color"].ToString();
                [B]objThis.Wheels = Wheel.GetAll(objThis.Name);    [/B]             
                objColl.Add(objThis);
            }
            return objColl;
        }

    }
}
Medlem sedan maj 20012 812 inlägg
#10

Eftersom du inte visar koden för Wheel.GetAll() är det svårt att svara på frågan, men om din Wheel.GetAll() ser ut som Car.GetAll() så bör även denna kod vara trådsäker, då du återigen opererar på nyskapade objekt inne i metoden som du anropar... så det bör vara trådsäkert.

- M

Medlem sedan jan. 20013 406 inlägg
#11

Japp den ser ut som Car så då har det jag har trott att det varit trådäskert nu bekräftats av en expert ;) Tack så mycket

Medlem sedan feb. 200563 inlägg
#12

Det finns olika nivåer på "thread-safety". Java-gurun Joshua Bloch definierar fem st. nivåer (i bokens 'Item 52', samtidigt som han poängterar att det inte finns några "widely accpeted conventions in this area" avseende terminologin/definitionerna) i sin prisbelönta bok "Effective Java Programming Language Guide ", varav en nivå är "immutable" vilket innebär att ett objekts tillstånd inte kan förändras och att det därför är riskfritt att dela det mellan olika trådar.
Angående Java vs C#.NET så är väldigt mycket i denna bok även applicerbart för C#, bl.a. koncepten om olika nivåer på thread-safety.

Ett relevant citat från Bloch:
"Moreover, the claim that the presence of the synchronized keyword is sufficient to document thread safety embodies the common misconception that thread safety is an all-or-nothing property. In fact, there are many levels of thread safety that a class can support."
(eventuellt nödvändig förklarande information till .NET-programmerare: java 'synchronized' = C# 'lock')

Vad är man då egentligen ute efter när man säger att man eftersträvar trådsäkerhet ?
Jo, det bör rimligtvis vara att man vill undvika de potentiella problem som kan uppstå i en flertrådad applikation.
Om man tittar på klassen 'Car' så kan man konstatera att den är "mutable" (går att förändra) eftersom tillståndet kan förändras via set-properties, och om instanser delas mellan olika trådar så kan faktiskt problem uppstå, beroende på hur klienterna använder klassen.
Antag t.ex. att huvudtråden i applikationen hämtar en lista med alla bilar så här:
'List<Car> cars = Car.getAll();'
och att den sedan skapar två nya trådar och till båda dessa skickar med en referens till 'cars' som de två trådarna lagrar i egna fält/medlemsvariabler.
Antag sedan t.ex. att en metod i en av trådarna innehåller följande kod:
(rad 1) List<string> attributes = this.cars[0].Attributes;
(rad 2) attributes.Add("abc");
(rad 3) string s = attributes[attributes.Count-1];

Efter exekveringen av rad 3 är det inte helt säkert att variabel s kommer att innehålla strängen "abc" eftersom det kan vara på det viset att mellan exkeveringen av rad 2 och rad 3 så har den andra tråden via sin referens till cars anropat t.ex. 'car [0].Attributes.Add("xyz")' och lagt till ytterligare ett attribut som den första tråden inte hade räknat med.

Gladh skrev:

Addeladde skrev:

Är detta trådsäkert?

Som jag kan se det så skulle detta vara trådsäkert eftersom private List<string> attributes inte kommer att delas mellan några trådar.

Du skapar en klass car i din static-metod, det betyder att denna klassen kommer få en egen minnesrymd för varje tråd som accessar metoden. Så jag kan inte se att du skulle få några problem med trådar i denna kod.

Jo, visserligen skapas både 'List<Car>' och 'Car' instanserna som lokala variabler i static-metoden 'Car.getAll()', men den anropande koden kan alltså spara en referens (och dela den referensen med andra trådar !) till det returnerade 'List<Car>'-objektet, och eftersom såväl List-objektet som de ingående Car-instanserna (och 'Car.Attributes'-propertyn) är "mutable" så kan man alltså få problem i en flertrådad applikation.

Om man själv implementerar klientkoden så kan man visserligen undvika att lagra och dela en referens till det returnerade objektet genom att varje tråd får själv anropa 'Car.getAll()' för att erhålla nya egna instanser till collectionen och car-objekten, så att ingen annan tråd samtidigt modifierar samma car-instanser.
Å andra sidan (i framtiden när applikationen har vuxit) kanske någon programmerare kan komma att få för sig att vilja "optimera" getAll-metoden och cacha den (d.v.s. lagra den i en referens istället för att skapa en collection vid varje anrop) med lite lazy loading så att koden som tidigare fungerade p.g.a. att klienten förväntade sig egna instanser, kan sluta att fungera tillfredsställande p.g.a. att flera trådar nu plötsligt modifierar samma instanser. Då man använder förändringsbara (mutable) objekt så kan alltså problem alltid tänkas uppstå om flera trådar delar referens till samma objekt, och den ena tråden förväntar sig att de modifieringar av tillstånd som den själv triggar kommer att bibehållas mellan metod-anropen.
Ett annat litet exempel, om en referens till ett car-objekt delas mellan flera trådar, så kanske den ena tråden vill sätta en färg innan en annan metod anropas:
car.Color = color;
car.MetodSomAnvanderColor();
men mellan dessa anrop så kan en annan tråd också ha ändrat Color på samma car-instans.

Medlem sedan maj 20012 812 inlägg
#13

TomasJ skrev:

Om man tittar på klassen 'Car' så kan man konstatera att den är "mutable" (går att förändra) eftersom tillståndet kan förändras via set-properties, och om instanser delas mellan olika trådar så kan faktiskt problem uppstå, beroende på hur klienterna använder klassen.

Det har du helt rätt i, vilket jag inte tänkte på i sammanhanget (mest för den krävde en längre tankebana än vad jag orkade uppbringa :))...

TomasJ skrev:

Efter exekveringen av rad 3 är det inte helt säkert att variabel s kommer att innehålla strängen "abc" eftersom det kan vara på det viset att mellan exkeveringen av rad 2 och rad 3 så har den andra tråden via sin referens till cars anropat t.ex. 'car [0].Attributes.Add("xyz")' och lagt till ytterligare ett attribut som den första tråden inte hade räknat med.

Det stämmer också, även om det är ett litet krystat exempel, eftersom tillståndet för instansen (om referensen delas mellan flera trådar) aldrig kan vara säker om du inte både sätter och läser det inom samma lås, eller som du påpekade objektet endast har läsrättigheter (vilket ju inte är så vanligt och inte jätte tillämpbart).

- M

263 ms totalt · 4 externa anrop · v20260731065814-full.86ec41c2
123 ms — deklarationer (db)
0 ms — hämta statistik (cache)
136 ms — hämta tråd, inlägg och bilagor (db)
124 ms — ändringar (db)