మీ అనువర్తనం యొక్క పనితీరును వేగవంతం చేయడానికి మల్టీథ్రెడింగ్ ఉపయోగించబడుతుంది, కానీ స్పీడ్ బూస్ట్ ఉచితం కాదు: సమాంతర థ్రెడ్లను నిర్వహించడానికి జాగ్రత్తగా ప్రోగ్రామింగ్ అవసరం మరియు సరైన జాగ్రత్తలు లేకుండా, మీరు రేసు పరిస్థితులు, డెడ్లాక్లు మరియు క్రాష్లకు కూడా వెళ్ళవచ్చు.
మల్టీథ్రెడింగ్ కష్టతరం చేస్తుంది?
మీరు మీ ప్రోగ్రామ్కు చెప్పకపోతే, మీ కోడ్ అంతా “ప్రధాన థ్రెడ్” పై నడుస్తుంది. మీ అప్లికేషన్ యొక్క ఎంట్రీ పాయింట్ నుండి, ఇది మీ అన్ని విధులను ఒకదాని తరువాత ఒకటి స్క్రోల్ చేస్తుంది మరియు చేస్తుంది. ఇది పనితీరు టోపీని కలిగి ఉంది, స్పష్టంగా మీరు అన్నింటినీ ఒకేసారి ప్రాసెస్ చేయవలసి వస్తే మాత్రమే మీరు చాలా ఎక్కువ చేయగలరు. చాలా ఆధునిక CPU లు 12 లేదా అంతకంటే ఎక్కువ థ్రెడ్లతో ఆరు లేదా అంతకంటే ఎక్కువ కోర్లను కలిగి ఉంటాయి, కాబట్టి మీరు దాన్ని ఉపయోగించకపోతే పనితీరు పట్టికలో ఉంటుంది.
అయితే, ఇది “మల్టీథ్రెడింగ్ను ఆన్ చేయడం” అంత సులభం కాదు. నిర్దిష్ట విషయాలు (ఉచ్చులు వంటివి) మాత్రమే తగినంతగా మల్టీథ్రెడ్ చేయబడతాయి మరియు దీన్ని చేసేటప్పుడు పరిగణనలోకి తీసుకోవలసినవి చాలా ఉన్నాయి.
మొదటి మరియు అతి ముఖ్యమైన ప్రశ్న టెండర్ పరిస్థితులు. బహుళ థ్రెడ్లు పంచుకున్న వనరును థ్రెడ్ సవరించినప్పుడు ఇవి తరచుగా వ్రాసే ఆపరేషన్ల సమయంలో జరుగుతాయి. ఇది ప్రవర్తనకు దారితీస్తుంది, ఇక్కడ ప్రోగ్రామ్ అవుట్పుట్ ఏ థ్రెడ్ మొదట ఏదో పూర్తి చేస్తుంది లేదా మారుస్తుంది అనే దానిపై ఆధారపడి ఉంటుంది, ఇది యాదృచ్ఛిక మరియు unexpected హించని ప్రవర్తనకు దారితీస్తుంది.
ఇవి చాలా, చాలా సరళంగా ఉంటాయి, ఉదాహరణకు, మీరు ఉచ్చుల మధ్య ఏదో నడుస్తున్న సంఖ్యను ఉంచాలి. దీన్ని చేయడానికి చాలా స్పష్టమైన మార్గం వేరియబుల్ను సృష్టించడం మరియు పెంచడం, కానీ ఇది థ్రెడ్ సురక్షితం కాదు.
ఈ జాతి పరిస్థితి సంభవిస్తుంది ఎందుకంటే ఇది నైరూప్య అర్థంలో “వేరియబుల్కు ఒకదాన్ని జోడించడం” గురించి మాత్రమే కాదు; CPU విలువను లోడ్ చేస్తోంది number
రిజిస్టర్లో, ఆ విలువకు ఒకదాన్ని జోడించి, ఫలితాన్ని కొత్త వేరియబుల్ విలువగా నిల్వ చేస్తుంది. అదే సమయంలో, మరొక థ్రెడ్ కూడా అదే విధంగా చేయటానికి ప్రయత్నిస్తుందని అతనికి తెలియదు మరియు త్వరలో తప్పు విలువను లోడ్ చేసింది number
. రెండు థ్రెడ్లు సంఘర్షణలో ఉన్నాయి మరియు లూప్ చివరిలో, number
ఇది 100 కి సమానంగా ఉండకపోవచ్చు.
దీన్ని నిర్వహించడానికి .NET ఒక లక్షణాన్ని అందిస్తుంది: ది lock
కీవర్డ్. ఇది ప్రత్యక్ష మార్పులు చేయకుండా మిమ్మల్ని ఆపదు, కానీ హాంగ్ పొందడానికి ఒకేసారి ఒక థ్రెడ్ను మాత్రమే అనుమతించడం ద్వారా సమన్వయాన్ని నిర్వహించడానికి ఇది సహాయపడుతుంది. మరొక థ్రెడ్ ప్రాసెస్ చేస్తున్నప్పుడు మరొక థ్రెడ్ బ్లాక్ ఇన్స్ట్రక్షన్ ఎంటర్ చెయ్యడానికి ప్రయత్నిస్తే, అది కొనసాగడానికి ముందు 300 ఎంఎస్ వరకు వేచి ఉంటుంది.
రిఫరెన్స్ రకాలను మాత్రమే లాక్ చేయవచ్చు, కాబట్టి ముందుగానే లాకింగ్ వస్తువును సృష్టించడం మరియు విలువ రకాన్ని లాక్ చేయడానికి ప్రత్యామ్నాయంగా ఉపయోగించడం ఒక సాధారణ నమూనా.
అయితే, ఇప్పుడు మరొక సమస్య ఉందని మీరు గమనించవచ్చు: ప్రతిష్ఠంభన. ఈ కోడ్ ఒక చెత్త ఉదాహరణ, కానీ ఇక్కడ ఇది రెగ్యులర్ మాదిరిగానే ఉంటుంది for
లూప్ (వాస్తవానికి కొంచెం నెమ్మదిగా ఉంటుంది, ఎందుకంటే అదనపు థ్రెడ్లు మరియు బ్లాక్స్ అదనపు ఓవర్ హెడ్). ప్రతి థ్రెడ్ లాక్ పొందడానికి ప్రయత్నిస్తుంది, కానీ ఒకేసారి ఒకటి మాత్రమే లాక్ కలిగి ఉంటుంది, కాబట్టి ఒకేసారి ఒక థ్రెడ్ మాత్రమే లాక్ లోపల కోడ్ను అమలు చేయగలదు. ఈ సందర్భంలో, ఇది లూప్ యొక్క మొత్తం కోడ్, కాబట్టి లాక్ స్టేట్మెంట్ థ్రెడింగ్ యొక్క అన్ని ప్రయోజనాలను తొలగిస్తుంది మరియు ప్రతిదీ నెమ్మదిగా చేస్తుంది.
సాధారణంగా, మీరు వ్రాసే ఆపరేషన్లు చేయవలసి వచ్చినప్పుడు అవసరమైనంతవరకు లాక్ చేయాలనుకుంటున్నారు. ఏదేమైనా, ఏమి నిరోధించాలో ఎన్నుకునేటప్పుడు మీరు పోటీని గుర్తుంచుకోవాలని మేము సిఫార్సు చేస్తున్నాము, ఎందుకంటే చదవడం కూడా ఎల్లప్పుడూ థ్రెడ్-సురక్షితం కాదు. మరొక థ్రెడ్ వస్తువుకు వ్రాస్తుంటే, మరొక థ్రెడ్ నుండి చదవడం తప్పు విలువను అందిస్తుంది లేదా ఒక నిర్దిష్ట పరిస్థితి తప్పు ఫలితాన్ని ఇవ్వడానికి కారణమవుతుంది.
అదృష్టవశాత్తూ, దీన్ని సరిగ్గా చేయడానికి కొన్ని ఉపాయాలు ఉన్నాయి, ఇక్కడ మీరు జాతి పరిస్థితులను నివారించడానికి బ్లాక్లను ఉపయోగిస్తున్నప్పుడు మల్టీథ్రెడింగ్ వేగాన్ని సమతుల్యం చేయవచ్చు.
అణు కార్యకలాపాల కోసం ఇంటర్లాక్డ్ ఉపయోగించండి
ప్రాథమిక కార్యకలాపాల కోసం, ఉపయోగించి lock
దావా అధికంగా ఉండవచ్చు. సంక్లిష్ట మార్పులకు ముందు లాక్ చేయడానికి ఇది చాలా ఉపయోగకరంగా ఉంటుంది, అయితే విలువను జోడించడం లేదా భర్తీ చేయడం వంటి వాటికి ఇది చాలా ఎక్కువ భారం.
ఇంటర్లాక్డ్ అనేది అదనంగా, పున ment స్థాపన మరియు పోలిక వంటి కొన్ని మెమరీ ఆపరేషన్లను కలిగి ఉన్న తరగతి. దిగువ పద్ధతులు CPU స్థాయిలో అమలు చేయబడతాయి మరియు అణు మరియు ప్రమాణం కంటే చాలా వేగంగా ఉంటాయి lock
ప్రకటన. వీలైనప్పుడల్లా వాటిని ఉపయోగించమని మేము సిఫార్సు చేస్తున్నాము, అయినప్పటికీ అవి బ్లాక్ను పూర్తిగా భర్తీ చేయవు.
పై ఉదాహరణలో, ప్యాడ్లాక్ను కాల్తో భర్తీ చేయండి Interlocked.Add()
ఇది ఆపరేషన్ చాలా వేగవంతం చేస్తుంది. ఈ సరళమైన ఉదాహరణ ఇంటర్లాక్డ్ను ఉపయోగించకపోవడం కంటే వేగంగా లేదు, ఇది పెద్ద ఆపరేషన్లో భాగంగా ఉపయోగపడుతుంది మరియు ఇప్పటికీ స్పీడ్ బూస్ట్.
కూడా ఉంది Increment
ఉంది Decrement
కోసం ++
ఉంది --
కార్యకలాపాలు, ఇది మీకు రెండు ఘన కీస్ట్రోక్లను ఆదా చేస్తుంది. వారు అక్షరాలా చుట్టుకుంటారు Add(ref count, 1)
హుడ్ కింద, కాబట్టి వాటిని ఉపయోగించడానికి నిర్దిష్ట వేగం లేదు.
మీరు ఎక్స్ఛేంజ్ను కూడా ఉపయోగించవచ్చు, ఇది ఒక సాధారణ పద్ధతి, దానికి ఇచ్చిన విలువకు సమానమైన వేరియబుల్ను సెట్ చేస్తుంది. అయినప్పటికీ, మీరు దీనితో జాగ్రత్తగా ఉండాలి: మీరు దానిని అసలు విలువను ఉపయోగించి లెక్కించిన విలువకు సెట్ చేస్తుంటే, అది థ్రెడ్ సురక్షితం కాదు, ఎందుకంటే ఇంటర్లాక్డ్.ఎక్స్ఛేంజ్ను అమలు చేయడానికి ముందు పాత విలువ మార్చబడి ఉండవచ్చు.
కంపేర్ ఎక్స్ఛేంజ్ రెండు విలువల సమానత్వం కోసం తనిఖీ చేస్తుంది మరియు అవి సమానంగా ఉంటే విలువను భర్తీ చేస్తుంది.
థ్రెడ్-సురక్షిత సేకరణలను ఉపయోగించండి
లో డిఫాల్ట్ సేకరణలు System.Collections.Generic
వాటిని మల్టీథ్రెడింగ్తో ఉపయోగించవచ్చు, కానీ అవి పూర్తిగా థ్రెడ్-సురక్షితం కాదు. మైక్రోసాఫ్ట్ కొన్ని సేకరణల యొక్క థ్రెడ్-సురక్షిత అమలులను అందిస్తుంది System.Collections.Concurrent
.
వీటిలో ఉన్నాయి ConcurrentBag
, క్రమబద్ధీకరించని సాధారణ సేకరణ మరియు ConcurrentDictionary,
థ్రెడ్-సేఫ్ డిక్షనరీ. ఏకకాలంలో క్యూలు మరియు స్టాక్లు కూడా ఉన్నాయి OrderablePartitioner
, ఇది ప్రతి థ్రెడ్కు ప్రత్యేక విభజనలుగా జాబితాలు వంటి క్రమబద్ధీకరించగల డేటా వనరులను విభజించగలదు.
ఉచ్చులను సమాంతరంగా చేయడానికి ప్రయత్నించండి
తరచుగా, మల్టీథ్రెడ్కు సులభమైన ప్రదేశం పెద్ద, ఖరీదైన ఉచ్చులలో ఉంటుంది. మీరు సమాంతరంగా బహుళ ఎంపికలను అమలు చేయగలిగితే, మొత్తం రన్ టైమ్లో మీరు విపరీతమైన వేగాన్ని పొందవచ్చు.
దీన్ని నిర్వహించడానికి ఉత్తమ మార్గం System.Threading.Tasks.Parallel
. ఈ తరగతి ప్రత్యామ్నాయాలను అందిస్తుంది for
ఉంది foreach
లూప్ యొక్క శరీరాలను ప్రత్యేక థ్రెడ్లలో అమలు చేసే ఉచ్చులు. దీనికి కొద్దిగా భిన్నమైన వాక్యనిర్మాణం అవసరం అయినప్పటికీ ఇది ఉపయోగించడం చాలా సులభం:
సహజంగానే, ఇక్కడ సమస్య ఏమిటంటే మీరు నిర్ధారించుకోవాలి DoSomething()
ఇది థ్రెడ్-సురక్షితం మరియు షేర్డ్ వేరియబుల్స్తో జోక్యం చేసుకోదు. అయినప్పటికీ, లూప్ను సమాంతర లూప్తో భర్తీ చేయడం ఎల్లప్పుడూ సులభం కాదు మరియు చాలా సందర్భాల్లో ఇది అవసరం lock
మార్పులు చేయడానికి భాగస్వామ్య వస్తువులు.
డెడ్లాక్లతో కొన్ని సమస్యలను తగ్గించడానికి, Parallel.For
ఉంది Parallel.ForEach
రాష్ట్రంతో వ్యవహరించడానికి అదనపు కార్యాచరణను అందిస్తుంది. సాధారణంగా, అన్ని పునరావృత్తులు ప్రత్యేక థ్రెడ్లో పనిచేయవు: మీకు 1000 మూలకాలు ఉంటే, 1000 థ్రెడ్లు సృష్టించబడవు; ఇది మీ CPU నిర్వహించగలిగే అనేక థ్రెడ్లను సృష్టిస్తుంది మరియు ప్రతి థ్రెడ్కు బహుళ పునరావృతాలను చేస్తుంది. దీని అర్థం మీరు మొత్తాన్ని లెక్కిస్తుంటే, ప్రతి పునరావృతానికి లాక్ చేయవలసిన అవసరం లేదు. మీరు ఉపమొత్తం వేరియబుల్లో పాస్ చేసి చివరికి ఆబ్జెక్ట్ను లాక్ చేసి ఒకసారి మార్పులు చేయవచ్చు. ఇది చాలా పెద్ద జాబితాలలో ఓవర్హెడ్ను తీవ్రంగా తగ్గిస్తుంది.
ఒక ఉదాహరణ చూద్దాం. కింది కోడ్ వస్తువుల యొక్క సుదీర్ఘ జాబితాను తీసుకుంటుంది మరియు ప్రతిదాన్ని JSON లోకి విడిగా సీరియలైజ్ చేయాలి, ఇది ఫైల్తో ముగుస్తుంది List<string>
అన్ని వస్తువుల. JSON సీరియలైజేషన్ చాలా నెమ్మదిగా జరిగే ప్రక్రియ, కాబట్టి ప్రతి మూలకాన్ని బహుళ థ్రెడ్లలో విభజించడం పెద్ద వేగం పెంచడం.
ఇక్కడ చాలా విషయాలు మరియు అన్ప్యాక్ చేయడానికి చాలా ఉన్నాయి:
- మొదటి వాదన IEnumerable ను అంగీకరిస్తుంది, ఇది లూప్ చేసిన డేటాను నిర్వచిస్తుంది. ఇది ఫోర్ఇచ్ లూప్, అయితే అదే కాన్సెప్ట్ బేసిక్ ఫర్ లూప్ల కోసం పనిచేస్తుంది.
- మొదటి చర్య స్థానిక ఉపమొత్త వేరియబుల్ను ప్రారంభిస్తుంది. ఈ వేరియబుల్ లూప్ యొక్క ప్రతి పునరావృతంలో భాగస్వామ్యం చేయబడుతుంది, కానీ ఒకే థ్రెడ్లో మాత్రమే. ఇతర థ్రెడ్లు వాటి స్వంత ఉప మొత్తాలను కలిగి ఉంటాయి. ఇక్కడ, మేము దానిని ఖాళీ జాబితాలో ప్రారంభిస్తున్నాము. మీరు సంఖ్యా మొత్తాన్ని లెక్కిస్తుంటే, మీరు చేయగలరు
return 0
ఇక్కడ. - రెండవ చర్య ప్రధాన చక్రం యొక్క శరీరం. మొదటి వాదన ప్రస్తుత మూలకం (లేదా ఫర్ లూప్లోని సూచిక), రెండవది మీరు పిలవడానికి ఉపయోగించే సమాంతర లూప్స్టేట్ వస్తువు.
.Break()
మరియు చివరిది మొత్తం టోటల్ వేరియబుల్.- ఈ చక్రంలో మూలకంపై పనిచేయడం మరియు ఉపమొత్తాన్ని సవరించడం సాధ్యమవుతుంది. తిరిగి వచ్చిన విలువ తదుపరి చక్రానికి ఉపమొత్తాన్ని భర్తీ చేస్తుంది. ఈ సందర్భంలో, మేము మూలకాన్ని స్ట్రింగ్లోకి సీరియల్ చేస్తాము, ఆపై నడుస్తున్న మొత్తానికి స్ట్రింగ్ను జోడిస్తాము, ఇది జాబితా.
- చివరగా, చివరి చర్య అన్ని పరుగుల చివరలో నడుస్తున్న మొత్తం యొక్క “ఫలితం” తీసుకుంటుంది, తుది మొత్తం ఆధారంగా వనరును లాక్ చేయడానికి మరియు సవరించడానికి మిమ్మల్ని అనుమతిస్తుంది. ఈ చర్య చివరికి ఒకసారి జరుగుతుంది, కానీ ఇది ఇప్పటికీ ప్రత్యేక థ్రెడ్లో జరుగుతుంది, కాబట్టి మీరు వనరులను సవరించడానికి ఇంటర్లాక్ పద్ధతులను లాక్ చేయాలి లేదా ఉపయోగించాలి. ఇక్కడ, మేము పిలుస్తాము
AddRange()
తుది జాబితాకు ఉపమొత్తం జాబితాను జోడించడానికి.
యూనిటీ మల్టీథ్రెడింగ్
ఒక చివరి గమనిక: మీరు యూనిటీ గేమ్ ఇంజిన్ను ఉపయోగిస్తుంటే, మీరు మల్టీథ్రెడింగ్తో జాగ్రత్తగా ఉండాలి. మీరు ఏ యూనిటీ API ని కాల్ చేయలేరు, లేకపోతే ఆట క్రాష్ అవుతుంది. ప్రధాన థ్రెడ్లో API ఆపరేషన్లను అమలు చేయడం ద్వారా మరియు మీరు ఏదైనా సమాంతరంగా అవసరమైనప్పుడు ముందుకు వెనుకకు మారడం ద్వారా మీరు దీన్ని తక్కువగా ఉపయోగించవచ్చు.
ప్రధానంగా, ఇది సన్నివేశం లేదా భౌతిక ఇంజిన్తో సంభాషించే ఆపరేషన్లకు వర్తిస్తుంది. వెక్టర్ 3 యొక్క గణిత ప్రభావితం కాదు మరియు మీరు ఎటువంటి సమస్యలు లేకుండా ప్రత్యేక థ్రెడ్ నుండి ఉపయోగించుకోవచ్చు. మీ వస్తువుల ఫీల్డ్లు మరియు లక్షణాలను మార్చడానికి కూడా మీరు స్వేచ్ఛగా ఉన్నారు, వారు హుడ్ కింద ఏ యూనిటీ ఆపరేషన్లను పిలవరు.