"यदि कोई कर्मचारी अपना काम अच्छी तरह से करना चाहता है, तो उसे पहले अपने औजारों को तेज करना होगा।" - कन्फ्यूशियस, "द एनालेक्ट्स ऑफ कन्फ्यूशियस। लू लिंगगोंग"
मुखपृष्ठ > प्रोग्रामिंग > सिंक हो जाओ।पूल और इसके पीछे के यांत्रिकी

सिंक हो जाओ।पूल और इसके पीछे के यांत्रिकी

2024-11-06 को प्रकाशित
ब्राउज़ करें:202

यह पोस्ट का एक अंश है; पूरी पोस्ट यहां उपलब्ध है: https://victoriametrics.com/blog/go-sync-pool/


यह पोस्ट गो में समवर्ती प्रबंधन के बारे में एक श्रृंखला का हिस्सा है:

  • सिंक पर जाएं। म्यूटेक्स: सामान्य और भुखमरी मोड
  • सिंक पर जाएं। प्रतीक्षा समूह और संरेखण समस्या
  • सिंक करें।पूल और इसके पीछे के यांत्रिकी (हम यहां हैं)
  • सिंक पर जाएं। कोंड, सबसे अधिक अनदेखा सिंक तंत्र

विक्टोरियामेट्रिक्स स्रोत कोड में, हम सिंक.पूल का बहुत उपयोग करते हैं, और यह ईमानदारी से अस्थायी वस्तुओं, विशेष रूप से बाइट बफ़र्स या स्लाइस को संभालने के लिए बहुत उपयुक्त है।

यह आमतौर पर मानक पुस्तकालय में उपयोग किया जाता है। उदाहरण के लिए, एन्कोडिंग/जेसन पैकेज में:

package json

var encodeStatePool sync.Pool

// An encodeState encodes JSON into a bytes.Buffer.
type encodeState struct {
    bytes.Buffer // accumulated output

    ptrLevel uint
    ptrSeen  map[any]struct{}
}

इस मामले में, *एनकोडस्टेट ऑब्जेक्ट का पुन: उपयोग करने के लिए सिंक.पूल का उपयोग किया जा रहा है, जो JSON को बाइट्स.बफर में एन्कोड करने की प्रक्रिया को संभालता है।

प्रत्येक उपयोग के बाद इन वस्तुओं को फेंकने के बजाय, जो केवल कचरा संग्रहकर्ता को अधिक काम देगा, हम उन्हें एक पूल (सिंक.पूल) में छिपा देते हैं। अगली बार जब हमें किसी ऐसी ही चीज़ की ज़रूरत होती है, तो हम उसे नए सिरे से बनाने के बजाय पूल से ही ले लेते हैं।

आपको नेट/http पैकेज में कई सिंक.पूल उदाहरण भी मिलेंगे, जिनका उपयोग I/O संचालन को अनुकूलित करने के लिए किया जाता है:

package http

var (
    bufioReaderPool   sync.Pool
    bufioWriter2kPool sync.Pool
    bufioWriter4kPool sync.Pool
)

जब सर्वर अनुरोध निकायों को पढ़ता है या प्रतिक्रियाएं लिखता है, तो यह अतिरिक्त आवंटन को छोड़कर, इन पूलों से पूर्व-आवंटित पाठक या लेखक को तुरंत खींच सकता है। इसके अलावा, 2 लेखक पूल, *bufioWriter2kPool और *bufioWriter4kPool, विभिन्न लेखन आवश्यकताओं को संभालने के लिए स्थापित किए गए हैं।

func bufioWriterPool(size int) *sync.Pool {
    switch size {
    case 2 



ठीक है, इतना ही परिचय काफी है।

आज, हम इस बात पर विचार कर रहे हैं कि सिंक.पूल क्या है, परिभाषा है, इसका उपयोग कैसे किया जाता है, हुड के नीचे क्या चल रहा है, और वह सब कुछ जो आप जानना चाहते हैं।

वैसे, यदि आप कुछ अधिक व्यावहारिक चाहते हैं, तो हमारे गो विशेषज्ञों का एक अच्छा लेख है जिसमें दिखाया गया है कि हम विक्टोरियामेट्रिक्स में सिंक.पूल का उपयोग कैसे करते हैं: समय श्रृंखला डेटाबेस में प्रदर्शन अनुकूलन तकनीक: सीपीयू-बाउंड संचालन के लिए सिंक.पूल

सिंक.पूल क्या है?

इसे सीधे शब्दों में कहें तो गो में सिंक.पूल एक ऐसी जगह है जहां आप अस्थायी वस्तुओं को बाद में पुन: उपयोग के लिए रख सकते हैं।

लेकिन यहां बात यह है कि आप यह नियंत्रित नहीं कर सकते कि पूल में कितनी वस्तुएं रहती हैं, और जो कुछ भी आप वहां डालते हैं उसे किसी भी समय, बिना किसी चेतावनी के हटाया जा सकता है और आपको अंतिम अनुभाग पढ़ते समय पता चल जाएगा कि ऐसा क्यों है।

अच्छी बात यह है कि पूल को थ्रेड-सुरक्षित बनाने के लिए बनाया गया है, इसलिए कई गोरोइन एक साथ इसमें टैप कर सकते हैं। कोई बड़ा आश्चर्य नहीं, यह देखते हुए कि यह सिंक पैकेज का हिस्सा है।

"लेकिन हम वस्तुओं का पुन: उपयोग करने से क्यों परेशान होते हैं?"

जब आपके पास एक साथ बहुत सारे गोरआउट्स चल रहे हों, तो उन्हें अक्सर समान वस्तुओं की आवश्यकता होती है। कल्पना करें कि go f() एक साथ कई बार चल रहा है।

यदि प्रत्येक गोरोइन अपने स्वयं के ऑब्जेक्ट बनाता है, तो मेमोरी का उपयोग तेजी से बढ़ सकता है और यह कचरा संग्रहकर्ता पर दबाव डालता है क्योंकि उसे उन सभी ऑब्जेक्ट्स को साफ करना पड़ता है जब उनकी आवश्यकता नहीं रह जाती है।

यह स्थिति एक चक्र बनाती है जहां उच्च संगामिति उच्च मेमोरी उपयोग की ओर ले जाती है, जो तब कचरा संग्रहकर्ता को धीमा कर देती है। सिंक.पूल को इस चक्र को तोड़ने में मदद करने के लिए डिज़ाइन किया गया है।

type Object struct {
    Data []byte
}

var pool sync.Pool = sync.Pool{
    New: func() any {
        return &Object{
            Data: make([]byte, 0, 1024),
        }
    },
}

एक पूल बनाने के लिए, आप एक नया() फ़ंक्शन प्रदान कर सकते हैं जो पूल खाली होने पर एक नया ऑब्जेक्ट लौटाता है। यह फ़ंक्शन वैकल्पिक है, यदि आप इसे प्रदान नहीं करते हैं, तो पूल खाली होने पर शून्य लौटाता है।

उपरोक्त स्निपेट में, लक्ष्य ऑब्जेक्ट स्ट्रक्चर इंस्टेंस, विशेष रूप से इसके अंदर के स्लाइस का पुन: उपयोग करना है।

स्लाइस का पुन: उपयोग करने से अनावश्यक वृद्धि को कम करने में मदद मिलती है।

उदाहरण के लिए, यदि उपयोग के दौरान स्लाइस 8192 बाइट्स तक बढ़ जाती है, तो आप इसे पूल में वापस डालने से पहले इसकी लंबाई को शून्य पर रीसेट कर सकते हैं। अंतर्निहित सरणी की क्षमता अभी भी 8192 है, इसलिए अगली बार जब आपको इसकी आवश्यकता होगी, तो वे 8192 बाइट्स पुन: उपयोग के लिए तैयार हैं।

func (o *Object) Reset() {
    o.Data = o.Data[:0]
}

func main() {
    testObject := pool.Get().(*Object)

    // do something with testObject

    testObject.Reset()
    pool.Put(testObject)
}

प्रवाह बहुत स्पष्ट है: आप पूल से एक वस्तु प्राप्त करते हैं, उसका उपयोग करते हैं, उसे रीसेट करते हैं, और फिर उसे वापस पूल में डाल देते हैं। ऑब्जेक्ट को रीसेट करना या तो इसे वापस रखने से पहले या पूल से प्राप्त करने के तुरंत बाद किया जा सकता है, लेकिन यह अनिवार्य नहीं है, यह एक सामान्य अभ्यास है।

यदि आप टाइप एसेरेशन पूल.गेट().(*ऑब्जेक्ट) का उपयोग करने के प्रशंसक नहीं हैं, तो इससे बचने के कुछ तरीके हैं:

  • पूल से ऑब्जेक्ट प्राप्त करने के लिए एक समर्पित फ़ंक्शन का उपयोग करें:
func getObjectFromPool() *Object {
    obj := pool.Get().(*Object)
    return obj
}
  • सिंक.पूल का अपना स्वयं का सामान्य संस्करण बनाएं:
type Pool[T any] struct {
    sync.Pool
}

func (p *Pool[T]) Get() T {
    return p.Pool.Get().(T)
}

func (p *Pool[T]) Put(x T) {
    p.Pool.Put(x)
}

func NewPool[T any](newF func() T) *Pool[T] {
    return &Pool[T]{
        Pool: sync.Pool{
            New: func() interface{} {
                return newF()
            },
        },
    }
}

जेनेरिक रैपर आपको प्रकार के दावों से बचते हुए, पूल के साथ काम करने का अधिक प्रकार-सुरक्षित तरीका देता है।

बस ध्यान दें, यह संकेत की अतिरिक्त परत के कारण थोड़ा सा ओवरहेड जोड़ता है। ज्यादातर मामलों में, यह ओवरहेड न्यूनतम है, लेकिन यदि आप अत्यधिक सीपीयू-संवेदनशील वातावरण में हैं, तो यह देखने के लिए बेंचमार्क चलाना एक अच्छा विचार है कि क्या यह इसके लायक है।

लेकिन रुकिए, इसमें और भी बहुत कुछ है।

सिंक.पूल और आवंटन जाल

यदि आपने पिछले कई उदाहरणों पर गौर किया है, जिसमें मानक लाइब्रेरी के उदाहरण भी शामिल हैं, तो हम पूल में जो संग्रहित करते हैं, वह आम तौर पर ऑब्जेक्ट नहीं होता है, बल्कि ऑब्जेक्ट के लिए एक सूचक होता है।

मैं एक उदाहरण से इसका कारण समझाता हूं:

var pool = sync.Pool{
    New: func() any {
        return []byte{}
    },
}

func main() {
    bytes := pool.Get().([]byte)

    // do something with bytes
    _ = bytes

    pool.Put(bytes)
}

हम []बाइट के एक पूल का उपयोग कर रहे हैं। आम तौर पर (हालांकि हमेशा नहीं), जब आप किसी इंटरफ़ेस में कोई मान पास करते हैं, तो यह मान को ढेर पर रखने का कारण बन सकता है। ऐसा यहां भी होता है, न केवल स्लाइस के साथ, बल्कि किसी भी चीज के साथ जिसे आप पूल में पास करते हैं।Put() जो एक सूचक नहीं है।

यदि आप एस्केप विश्लेषण का उपयोग करके जांच करते हैं:

// escape analysis
$ go build -gcflags=-m

bytes escapes to heap

अब, मैं यह नहीं कहता कि हमारे वेरिएबल बाइट्स ढेर में चले जाते हैं, मैं कहूंगा कि "बाइट्स का मान इंटरफ़ेस के माध्यम से ढेर में चला जाता है"।

वास्तव में ऐसा क्यों होता है यह जानने के लिए, हमें इस बात पर गौर करना होगा कि एस्केप विश्लेषण कैसे काम करता है (जो हम किसी अन्य लेख में कर सकते हैं)। हालाँकि, यदि हम पूल.पुट() के लिए एक पॉइंटर पास करते हैं, तो कोई अतिरिक्त आवंटन नहीं होता है:

var pool = sync.Pool{
    New: func() any {
        return new([]byte)
    },
}

func main() {
    bytes := pool.Get().(*[]byte)

    // do something with bytes
    _ = bytes

    pool.Put(bytes)
}

एस्केप विश्लेषण फिर से चलाएँ, आप देखेंगे कि अब ढेर में कोई एस्केप नहीं है। यदि आप अधिक जानना चाहते हैं, तो गो स्रोत कोड में एक उदाहरण है।

सिंक.पूल आंतरिक

इससे पहले कि हम जानें कि सिंक.पूल वास्तव में कैसे काम करता है, गो के पीएमजी शेड्यूलिंग मॉडल की बुनियादी बातों पर पकड़ बनाना जरूरी है, यह वास्तव में इस बात की रीढ़ है कि सिंक.पूल इतना कुशल क्यों है।

एक अच्छा लेख है जो कुछ दृश्यों के साथ पीएमजी मॉडल को तोड़ता है: गो में पीएमजी मॉडल

यदि आप आज आलसी महसूस कर रहे हैं और एक सरलीकृत सारांश की तलाश में हैं, तो मैं आपका साथ दूंगा:

PMG का मतलब P (लॉजिकल processors), M (machine threads), और G (goroutines) है। मुख्य बिंदु यह है कि प्रत्येक लॉजिकल प्रोसेसर (पी) पर किसी भी समय केवल एक मशीन थ्रेड (एम) चल सकता है। और एक गोरोइन (जी) को चलाने के लिए, इसे एक थ्रेड (एम) से जोड़ा जाना चाहिए।

Go sync.Pool and the Mechanics Behind It

पीएमजी मॉडल

यह 2 मुख्य बिंदुओं पर आधारित है:

  1. यदि आपके पास एन लॉजिकल प्रोसेसर (पी) है, तो आप समानांतर में एन गोरोइन तक चला सकते हैं, जब तक आपके पास कम से कम एन मशीन थ्रेड (एम) उपलब्ध हैं।
  2. किसी भी एक समय में, केवल एक गोरोइन (जी) एक प्रोसेसर (पी) पर चल सकता है। इसलिए, जब कोई P1 ​​G के साथ व्यस्त होता है, तो कोई भी अन्य G उस P1 पर तब तक नहीं चल सकता जब तक कि वर्तमान G या तो अवरुद्ध न हो जाए, समाप्त न हो जाए, या उसे मुक्त करने के लिए कुछ और न हो जाए।

लेकिन बात यह है कि, गो में एक सिंक.पूल सिर्फ एक बड़ा पूल नहीं है, यह वास्तव में कई 'स्थानीय' पूलों से बना है, जिनमें से प्रत्येक एक विशिष्ट प्रोसेसर संदर्भ या पी से जुड़ा हुआ है, जो कि गो का रनटाइम है किसी भी समय प्रबंधन करना।

Go sync.Pool and the Mechanics Behind It

स्थानीय पूल

जब प्रोसेसर (पी) पर चलने वाले गोरोइन को पूल से किसी ऑब्जेक्ट की आवश्यकता होती है, तो वह कहीं और देखने से पहले अपने स्वयं के पी-स्थानीय पूल की जांच करेगा।


पूरी पोस्ट यहां उपलब्ध है: https://victoriametrics.com/blog/go-sync-pool/

विज्ञप्ति वक्तव्य यह आलेख यहां पुन: प्रस्तुत किया गया है: https://dev.to/func25/go-syncpool-and-the-mechanics-behind-it-52c1?1 यदि कोई उल्लंघन है, तो कृपया इसे हटाने के लिए [email protected] से संपर्क करें।
नवीनतम ट्यूटोरियल अधिक>

चीनी भाषा का अध्ययन करें

अस्वीकरण: उपलब्ध कराए गए सभी संसाधन आंशिक रूप से इंटरनेट से हैं। यदि आपके कॉपीराइट या अन्य अधिकारों और हितों का कोई उल्लंघन होता है, तो कृपया विस्तृत कारण बताएं और कॉपीराइट या अधिकारों और हितों का प्रमाण प्रदान करें और फिर इसे ईमेल पर भेजें: [email protected] हम इसे आपके लिए यथाशीघ्र संभालेंगे।

Copyright© 2022 湘ICP备2022001581号-3