লক স্টেটমেন্টের ভিতরে আমার কত কাজ করা উচিত?


27

আমি একজন জুনিয়র বিকাশকারী, সফ্টওয়্যারটির জন্য একটি আপডেট লেখার বিষয়ে কাজ করছি যা কোনও তৃতীয় পক্ষের সমাধান থেকে ডেটা গ্রহণ করে, এটি একটি ডেটাবেজে সংরক্ষণ করে এবং তারপরে অন্য তৃতীয় পক্ষের সমাধানের জন্য ডেটা শর্ত করে। আমাদের সফ্টওয়্যার একটি উইন্ডোজ পরিষেবা হিসাবে চলমান।

পূর্ববর্তী সংস্করণ থেকে কোডটি দেখছি, আমি এটি দেখতে পাচ্ছি:

        static Object _workerLocker = new object();
        static int _runningWorkers = 0;
        int MaxSimultaneousThreads = 5;

        foreach(int SomeObject in ListOfObjects)
        {
            lock (_workerLocker)
            {
                while (_runningWorkers >= MaxSimultaneousThreads)
                {
                    Monitor.Wait(_workerLocker);
                }
            }

            // check to see if the service has been stopped. If yes, then exit
            if (this.IsRunning() == false)
            {
                break;
            }

            lock (_workerLocker)
            {
                _runningWorkers++;
            }

            ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);

        }

যুক্তিটি সুস্পষ্ট বলে মনে হচ্ছে: থ্রেড পুলে রুমের জন্য অপেক্ষা করুন, পরিষেবাটি বন্ধ হয়নি তা নিশ্চিত করুন, তারপরে থ্রেডের কাউন্টারটি বাড়িয়ে দিন এবং কাজটি সারি করুন। তারপরে কল করা একটি স্টেটমেন্টের ভিতরে _runningWorkersহ্রাস করা হয় ।SomeMethod()lockMonitor.Pulse(_workerLocker)

আমার প্রশ্ন হ'ল: একটি কোডের অভ্যন্তরে সমস্ত কোডকে ভাগ করে নেওয়ার কোনও সুবিধা আছে কি lock:

        static Object _workerLocker = new object();
        static int _runningWorkers = 0;
        int MaxSimultaneousThreads = 5;

        foreach (int SomeObject in ListOfObjects)
        {
            // Is doing all the work inside a single lock better?
            lock (_workerLocker)
            {
                // wait for room in ThreadPool
                while (_runningWorkers >= MaxSimultaneousThreads) 
                {
                    Monitor.Wait(_workerLocker);
                }
                // check to see if the service has been stopped.
                if (this.IsRunning())
                {
                    ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
                    _runningWorkers++;                  
                }
                else
                {
                    break;
                }
            }
        }

দেখে মনে হচ্ছে, এটি অন্যান্য থ্রেডগুলির জন্য কিছুটা বেশি অপেক্ষা করতে পারে, তবে তারপরে মনে হচ্ছে একক লজিক্যাল ব্লকে বারবার লক করা কিছুটা সময়সাপেক্ষও হবে। তবে, আমি মাল্টি-থ্রেডিংয়ে নতুন, তাই আমি ধরে নিচ্ছি যে এখানে অন্যান্য উদ্বেগ রয়েছে যেগুলি সম্পর্কে আমি অজানা।

_workerLockerলক হয়ে যাওয়া কেবলমাত্র অন্য কোনও জায়গাগুলিতেই রয়েছে SomeMethod()এবং কেবল হ্রাসের উদ্দেশ্যে _runningWorkers, এবং তারপরে লগিং এবং ফিরে আসার আগে শূন্যে যাওয়ার foreachসংখ্যা অপেক্ষা করার জন্য _runningWorkers

কোন সাহায্যের জন্য ধন্যবাদ।

সম্পাদনা 4/8/15

Semaphore ব্যবহারের সুপারিশের জন্য @ ডেলানকে ধন্যবাদ। কোডটি হয়ে যায়:

        static int MaxSimultaneousThreads = 5;
        static Semaphore WorkerSem = new Semaphore(MaxSimultaneousThreads, MaxSimultaneousThreads);

        foreach (int SomeObject in ListOfObjects)
        {
            // wait for an available thread
            WorkerSem.WaitOne();

            // check if the service has stopped
            if (this.IsRunning())
            {
                ThreadPool.QueueUserWorkItem(SomeMethod, SomeObject);
            }
            else
            {
                break;
            }
        }

WorkerSem.Release()ভিতরে বলা হয় SomeMethod()


1
যদি পুরো ব্লকটি লক করা থাকে, তবে সামমথোদ কীভাবে লকটিকে হ্রাস _ রিনিং ওয়ার্কার্সে পাবেন?
রাসেল আইএসসি

@ রাসেল্লাটিআইএসসি: থ্রেডপুল.কুইউউজারওয়ার্কআইটেমকে অবিচ্ছিন্নভাবে কল করা হয়েছে SomeMethod, উপরের "লক" বিভাগটি নতুন থ্রেডটি SomeMethodচালানো শুরু হওয়ার কমপক্ষে বা তারপরে ছেড়ে দেওয়া হবে ।
ডক ব্রাউন

ভাল যুক্তি. এটি আমার বোঝা যায় যে Monitor.Wait()লকটি প্রকাশ এবং পুনরায় অধিগ্রহণের উদ্দেশ্য যাতে অন্য কোনও উত্স ( SomeMethod, এই ক্ষেত্রে) এটি ব্যবহার করতে পারে। অন্য প্রান্তে, SomeMethodলকটি গ্রহণ করে, কাউন্টারটি হ্রাস করে এবং তারপরে কলগুলি Monitor.Pulse()প্রশ্নবিদ্ধ পদ্ধতিতে লকটি ফিরিয়ে দেয়। আবার এটি আমার নিজের বোঝাপড়া।
জোসেফ

@ ডক, এটি মিস করেছেন, তবে এখনও ... মনে হচ্ছে সামমথোদকে পরবর্তী পুনরাবৃত্তির উপর ভবিষ্যদ্বাণীটি লক হওয়ার আগেই শুরু করা দরকার ছিল অথবা "((রুনিং ওয়ার্কার্স> = ম্যাক্সিমস্লটেনিয়াস থ্রেডস))" থাকা অবস্থায় লকটির বিরুদ্ধে ঝুলানো হবে।
রাসেল আইএসসি

@ রাসেল্লিটআইএসসি: জোসেফ যেমন ইতিমধ্যে বলেছে: Monitor.Waitলকটি প্রকাশ করে। আমি ডক্সটি একবার দেখার পরামর্শ দিই।
ডক ব্রাউন

উত্তর:


33

এটি পারফরম্যান্সের প্রশ্ন নয়। এটি প্রথম এবং সর্বাগ্রে সঠিকতার প্রশ্ন। আপনার যদি দুটি লক স্টেটমেন্ট থাকে তবে আপনি তাদের মধ্যে ছড়িয়ে পড়া অপারেশনের জন্য বা আংশিকভাবে লক স্টেটমেন্টের বাইরে পারমাণবিকতার গ্যারান্টি দিতে পারবেন না। আপনার কোডটির পুরানো সংস্করণটির জন্য তৈরি, এর অর্থ:

এর শেষ while (_runningWorkers >= MaxSimultaneousThreads)এবং এর মধ্যে _runningWorkers++, যা কিছু ঘটতে পারে , কারণ কোড সমর্পণ করে এবং এর মধ্যে লকটি পুনরায় অর্জন করে। উদাহরণস্বরূপ, থ্রেড এ প্রথমবারের জন্য লকটি অর্জন করতে পারে, অন্য কোনও থ্রেড না বের হওয়া পর্যন্ত অপেক্ষা করুন এবং তারপরে লুপটি এবং এর বাইরে বেরিয়ে যায় lock। এরপরে এটি প্রিম্প্ট করা হয় এবং থ্রেড বি ছবিতে প্রবেশ করে, থ্রেড পুলে রুমের জন্য অপেক্ষা করছে। কারণ বলেন অন্যান্য থ্রেড প্রস্থান, সেখানে হয় রুম তাই এটি সব সময়ে খুব দীর্ঘ অপেক্ষা করে না। থ্রেড এ এবং থ্রেড বি উভয়ই এখন কিছু ক্রমে চলেছে, প্রতিটি বৃদ্ধি _runningWorkersএবং তাদের কাজ শুরু করে।

এখন পর্যন্ত আমি দেখতে পেলাম এমন কোনও ডেটা রেস নেই, তবে যৌক্তিকভাবে এটি ভুল, যেহেতু এখন MaxSimultaneousThreadsকর্মীদের চেয়ে বেশি চলছে। থ্রেড পুলে স্লট নেওয়ার কাজটি পারমাণবিক নয় বলে চেকটি (মাঝে মাঝে) অকার্যকর। এটি আপনাকে লক গ্রানুলারিটির আশেপাশে ছোট অপ্টিমাইজেশনের চেয়ে বেশি উদ্বেগ করা উচিত! (দ্রষ্টব্য যে বিপরীতে, খুব তাড়াতাড়ি বা খুব বেশি সময়ের জন্য লক করা সহজেই অচল অবস্থায় জন্মাতে পারে))

দ্বিতীয় স্নিপেট এই সমস্যাটি সমাধান করে, যতদূর আমি দেখতে পাচ্ছি। সমস্যার সমাধানের জন্য কিছুটা আক্রমণাত্মক পরিবর্তন সম্ভবত প্রথম লক স্টেটমেন্টের অভ্যন্তরে চেহারাটির ++_runningWorkersপরে ডান দিক স্থাপন করা হতে পারে while

এখন, শুদ্ধি একপাশে, অভিনয় সম্পর্কে কি? এটা বলা শক্ত। সাধারণত দীর্ঘ সময়ের জন্য লক করা ("মোটা") সম্মতি বাধা দেয়, তবে আপনি যেমনটি বলেছিলেন, সূক্ষ্ম দানযুক্ত লকিংয়ের অতিরিক্ত সিঙ্ক্রোনাইজেশন থেকে ওভারহেডের বিরুদ্ধে এটি ভারসাম্যপূর্ণ হওয়া দরকার। সাধারণত একমাত্র সমাধান হ'ল বেঞ্চমার্কিং এবং সচেতন হওয়া যে "সর্বত্র সমস্ত কিছু লক করুন" এবং "কেবলমাত্র সর্বনিম্ন লক করুন" এর চেয়ে বেশি বিকল্প রয়েছে। এখানে নিদর্শন এবং সম্মতিযুক্ত আদিম এবং থ্রেড-নিরাপদ ডেটা স্ট্রাকচার উপলব্ধ a উদাহরণস্বরূপ, এটি দেখে মনে হয় যে খুব অ্যাপ্লিকেশন সেমফোর্ডগুলি আবিষ্কার করা হয়েছিল, সুতরাং এই হাত-ঘূর্ণিত হ্যান্ড-লক করা কাউন্টারটির পরিবর্তে এর মধ্যে একটির ব্যবহার বিবেচনা করুন।


11

আপনি যে ভুল প্রশ্নটি জিজ্ঞাসা করছেন - আপনার দক্ষতা বাণিজ্য সম্পর্কে খুব বেশি যত্ন নেওয়া উচিত নয়, তবে সঠিকতার বিষয়ে more

প্রথম রূপটি নিশ্চিত করে _runningWorkersযে কেবলমাত্র একটি লক চলাকালীন অ্যাক্সেস করা হয়েছে, তবে এটি প্রথম ক্ষেত্রে _runningWorkersএবং দ্বিতীয়টির মধ্যে ব্যবধানে অন্য থ্রেড দ্বারা পরিবর্তিত হতে পারে এমন ক্ষেত্রে মিস করে । সত্যিই, কোডটি আমাকে দেখে মনে হচ্ছে যদি কেউ _runningWorkersএর প্রভাবগুলি এবং সম্ভাব্য ত্রুটিগুলি সম্পর্কে চিন্তা না করেই অ্যাক্সেস পয়েন্টগুলির অন্ধভাবে লক স্থাপন করে । ব্লকের breakভিতরে বিবৃতি কার্যকর করতে লেখককে কিছু কুসংস্কারের আশঙ্কা ছিল lock, তবে কে জানে?

সুতরাং আপনার প্রকৃতপক্ষে দ্বিতীয় বৈকল্পটি ব্যবহার করা উচিত, এর কম বা কম দক্ষতার কারণে নয়, কারণ এটির (আশাবাদী) প্রথমটির চেয়ে আরও সঠিক।


ফ্লিপ দিকে, অন্য কোনও লক অর্জনের প্রয়োজন হতে পারে এমন কোনও কাজ করার সময় একটি লক ধরে রাখা একটি অচলাবস্থার কারণ হতে পারে যা খুব কমই "সঠিক" আচরণ হিসাবে অভিহিত হতে পারে। একটিকে নিশ্চিত করা উচিত যে সমস্ত কোড যা ইউনিট হিসাবে সম্পন্ন করা দরকার তা একটি সাধারণ লক দ্বারা বেষ্টিত রয়েছে, তবে সেই লক জিনিসগুলির বাইরে এমন একটি পদক্ষেপ নেওয়া উচিত যা সেই ইউনিটের অংশ হওয়ার দরকার নেই, বিশেষত এমন জিনিসগুলির জন্য যা অন্যান্য লকগুলির অধিগ্রহণের প্রয়োজন হতে পারে ।
সুপারক্যাট

@ সুপের্যাট: এটি এখানে নয়, দয়া করে মূল প্রশ্নের নীচের মন্তব্যগুলি পড়ুন।
ডক ব্রাউন

9

অন্যান্য উত্তরগুলি বেশ ভাল এবং স্পষ্টভাবে সঠিকতার উদ্বেগগুলির সমাধান করে। আপনার আরও সাধারণ প্রশ্ন আমাকে সম্বোধন করুন:

লক স্টেটমেন্টের ভিতরে আমার কত কাজ করা উচিত?

আসুন স্ট্যান্ডার্ড পরামর্শ দিয়ে শুরু করা যাক, আপনি গৃহীত উত্তরের চূড়ান্ত অনুচ্ছেদে ডেলানানকে ইঙ্গিত করেছেন এবং ডেলানান:

  • কোনও নির্দিষ্ট অবজেক্ট লক করার সময় যতটা সম্ভব কাজ করুন। দীর্ঘ সময় ধরে রাখা লকগুলি বিতর্ক সাপেক্ষে এবং বিতর্কটি ধীরে ধীরে। মনে রাখবেন যে এই যে বোঝা যে মোট মধ্যে কোডের পরিমাণ নির্দিষ্ট লক এবং মোট কোড পরিমাণ সব লক বিবৃতি যে একই বস্তুর উপর লক উভয় প্রাসঙ্গিক।

  • ডেডলক (বা লাইভলক) হওয়ার সম্ভাবনা কমিয়ে আনার জন্য যতটা সম্ভব লক রয়েছে।

চতুর পাঠক লক্ষ করবেন যে এগুলি বিপরীত। প্রথম পয়েন্টটি বিতর্ক এড়াতে বড় ছোট লকগুলি অনেক ছোট, সূক্ষ্ম দানযুক্ত লকগুলিতে ভাঙ্গার পরামর্শ দেয়। দ্বিতীয়টি অচল লক এড়াতে একই লক অবজেক্টে স্বতন্ত্র লকগুলি একীকরণের পরামর্শ দেয়।

সর্বোত্তম স্ট্যান্ডার্ড পরামর্শ সম্পূর্ণরূপে বিপরীত তা থেকে আমরা কী উপসংহার নিতে পারি? আমরা আসলে ভাল পরামর্শ পেতে পারি:

  • প্রথমে সেখানে যাবেন না। আপনি যদি থ্রেডগুলির মধ্যে স্মৃতি ভাগ করে নিচ্ছেন তবে আপনি নিজেকে ব্যথার জগতের জন্য উন্মুক্ত করছেন।

আমার পরামর্শটি হ'ল, যদি আপনি সম্মতি চান তবে আপনার একচেটিয়া হিসাবে ইউনিট হিসাবে প্রক্রিয়াগুলি ব্যবহার করুন। আপনি যদি প্রক্রিয়াগুলি ব্যবহার করতে না পারেন তবে অ্যাপ্লিকেশন ডোমেনগুলি ব্যবহার করুন। আপনি যদি অ্যাপ্লিকেশন ডোমেন ব্যবহার করতে না পারেন, তবে আপনার থ্রেডগুলি টাস্ক সমান্তরাল গ্রন্থাগার দ্বারা পরিচালিত করুন এবং আপনার কোডটি নিম্ন-স্তরের থ্রেড (কর্মী) না দিয়ে উচ্চ-স্তরের টাস্ক (কাজের) পদে লিখুন।

আপনার যদি একেবারে ইতিবাচকভাবে থ্রেড বা সেমোফোরগুলির মতো নিম্ন-স্তরের সম্মিলিত আদিম ব্যবহার করা উচিত, তবে এগুলি আপনার উচ্চতর স্তরের বিমূর্তি তৈরি করতে ব্যবহার করুন যা আপনার সত্যিকারের প্রয়োজনের বিষয়টিকে ক্যাপচার করে। আপনি সম্ভবত দেখতে পাবেন যে উচ্চ স্তরের বিমূর্ততা "ব্যবহারকারী দ্বারা বাতিল করা যেতে পারে এমন একটি কাজ সম্পাদন করুন" এর মতো কিছু, এবং আরে, টিপিএল ইতিমধ্যে এটি সমর্থন করে, সুতরাং আপনার নিজের রোল করার দরকার নেই। আপনি সম্ভবত খুঁজে পাবেন যে আপনার থ্রেড নিরাপদ অলস সূচনার মতো কিছু দরকার; আপনার নিজস্ব, রোল না Lazy<T>যা বিশেষজ্ঞরা লিখেছিলেন। বিশেষজ্ঞদের দ্বারা রচিত থ্রেডসেফ সংগ্রহগুলি (পরিবর্তনযোগ্য বা অন্যথায়) ব্যবহার করুন। বিমূর্তনের স্তর যতটা সম্ভব উঁচুতে সরান।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.