থ্রেড-নিরাপদ অভিধান প্রয়োগের সর্বোত্তম উপায় কোনটি?


109

আইডি অভিধান থেকে প্রাপ্ত এবং একটি বেসরকারী সিঙ্করুট অবজেক্টটি সংজ্ঞায়িত করে আমি সি # তে একটি থ্রেড-নিরাপদ অভিধান প্রয়োগ করতে সক্ষম হয়েছি:

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public object SyncRoot
    {
        get { return syncRoot; }
    } 

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
    }

    // more IDictionary members...
}

আমি তখন আমার গ্রাহকরা (একাধিক থ্রেড) জুড়ে এই সিঙ্কআরট বস্তুটি লক করেছি:

উদাহরণ:

lock (m_MySharedDictionary.SyncRoot)
{
    m_MySharedDictionary.Add(...);
}

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


3
আপনি এটি সম্পর্কে কুৎসিত কি এটি?
আসফ আর

1
আমি মনে করি তিনি তার সমস্ত কোড লক স্টেটমেন্টগুলিকে শ্যাটারডিয়েশনারি ক্লাসের গ্রাহকদের মধ্যে উল্লেখ করছেন - তিনি যখনই শর্টড অভিধান অভিধানে কোনও পদ্ধতিতে অ্যাক্সেস করছেন তখনই তিনি কলিং কোডটিতে লক করছেন ।
পিটার মায়ার

অ্যাড মেথড ব্যবহার না করে প্রাক্তন- m_MySharedDedia ["key1"] = "আইটেম 1" নির্ধারণ করে চেষ্টা করুন, এটি থ্রেড নিরাপদ।
পরীক্ষার্থী

উত্তর:


43

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

public class SafeDictionary<TKey, TValue>: IDictionary<TKey, TValue>
{
    private readonly object syncRoot = new object();
    private Dictionary<TKey, TValue> d = new Dictionary<TKey, TValue>();

    public void Add(TKey key, TValue value)
    {
        lock (syncRoot)
        {
            d.Add(key, value);
        }
        OnItemAdded(EventArgs.Empty);
    }

    public event EventHandler ItemAdded;

    protected virtual void OnItemAdded(EventArgs e)
    {
        EventHandler handler = ItemAdded;
        if (handler != null)
            handler(this, e);
    }

    // more IDictionary members...
}

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


@ ফ্রাইগাইবব: আমি সংশ্লেষনের বস্তুটি প্রকাশ করার কারণেই কেবল গণনা ছিল। কনভেনশন দ্বারা, আমি যখন কেবল সংগ্রহের মাধ্যমে গণনা করব তখনই আমি সেই অবজেক্টটিতে একটি লক সম্পাদন করব।
জিপি

1
যদি আপনার অভিধানটি খুব বেশি বড় না হয় আপনি একটি অনুলিপিতে অঙ্ক করতে পারেন এবং এটি শ্রেণিতে তৈরি করতে পারেন।
fryguybob

2
আমার অভিধান খুব বড় নয়, এবং আমি মনে করি এটি কৌশলটি করেছে। আমি যা করেছি তা হ'ল কপিরাইটএইনম () নামে একটি নতুন পাবলিক পদ্ধতি তৈরি করা হয়েছে যা ব্যক্তিগত অভিধানের অনুলিপি সহ কোনও অভিধানের নতুন উদাহরণ দেয়। এই পদ্ধতিটি তখন অনুমানের জন্য ডাকা হয়েছিল, এবং সিঙ্করুটটি সরানো হয়েছিল। ধন্যবাদ!
জিপি

12
অভিধানের ক্রিয়াকলাপগুলি দানাদার হওয়ার কারণে এটি কোনও অন্তর্নিহিত থ্রেডসফ ক্লাস নয়। যদি (ডিক্ট। কনটেইনস (যাই হোক না কেন)) এর লাইন ধরে একটি সামান্য যুক্তি {ডিক্ট। সরান (যাই হোক না কেন); ডিক.এড (যা-ই হোক না কেন, নতুন); assured অবশ্যই একটি দৌড়ের অবস্থা হওয়ার জন্য অপেক্ষা করছে।
থামাল

207

সম্মতিতে সমর্থন করে। নেট নেট ক্লাসটির নাম দেওয়া হয়েছে ConcurrentDictionary


4
দয়া করে এটিকে প্রতিক্রিয়া হিসাবে চিহ্নিত করুন, নিজস্ব থাকলে আপনার পছন্দসই স্বৈরশাসকের প্রয়োজন নেই N নেট এর সমাধান রয়েছে
আলবার্তো

27
(মনে রাখবেন যে অন্য উত্তরটি .NET 4.0 (2010 সালে প্রকাশিত হয়েছিল) এর অস্তিত্বের অনেক আগে লেখা হয়েছিল))
জেপ্প স্টিগ নীলসেন

1
দুর্ভাগ্যক্রমে এটি কোনও লক-মুক্ত সমাধান নয়, সুতরাং এটি এসকিউএল সার্ভার সিএলআর নিরাপদ সমাবেশগুলিতে অকেজো। এখানে বর্ণিত মতামতগুলির মতো আপনার কিছু দরকার ছিল: cse.chalmers.se/~tsigas/papers/Lock-Free_D शब्दको.pdf বা সম্ভবত এই বাস্তবায়ন: github.com/hackraft/Ariadne
Triynko

2
সত্যই পুরানো, আমি জানি, তবে এটি লক্ষণীয় গুরুত্বপূর্ণ যে কনসকন্টার অভিধান অভিধান বনাম একটি অভিধান ব্যবহারের ফলে কার্যকর পারফরম্যান্সের ক্ষতি হতে পারে। এটি সম্ভবত ব্যয়বহুল প্রসঙ্গে স্যুইচিংয়ের ফলস্বরূপ, তাই নিশ্চিত হয়ে নিন যে কোনওটি ব্যবহারের আগে আপনার থ্রেড-নিরাপদ অভিধানের প্রয়োজন।
outbred

60

অভ্যন্তরীণভাবে সিঙ্ক্রোনাইজ করার চেষ্টাটি প্রায় নিশ্চিতভাবেই অপ্রতুল হবে কারণ এটি বিমূর্ততার খুব কম মাত্রায়। বলুন যে আপনি Addএবং ContainsKeyঅপারেশনগুলি পৃথকভাবে থ্রেড-নিরাপদ হিসাবে তৈরি করেছেন:

public void Add(TKey key, TValue value)
{
    lock (this.syncRoot)
    {
        this.innerDictionary.Add(key, value);
    }
}

public bool ContainsKey(TKey key)
{
    lock (this.syncRoot)
    {
        return this.innerDictionary.ContainsKey(key);
    }
}

তারপরে আপনি যখন একাধিক থ্রেড থেকে এই অনুমিত থ্রেড-সেফ কোডটিকে কল করবেন তখন কি হবে? এটি কি সর্বদা ঠিক কাজ করবে?

if (!mySafeDictionary.ContainsKey(someKey))
{
    mySafeDictionary.Add(someKey, someValue);
}

সহজ উত্তরটি হ'ল না। এক পর্যায়ে Addপদ্ধতিটি একটি ব্যতিক্রম ছুঁড়ে দেবে তা নির্দেশ করে যে অভিধানটিতে কীটি ইতিমধ্যে বিদ্যমান রয়েছে। থ্রেড-নিরাপদ অভিধানের সাথে এটি কীভাবে হতে পারে, আপনি জিজ্ঞাসা করতে পারেন? কেবলমাত্র প্রতিটি ক্রিয়াকলাপ থ্রেড-নিরাপদ, দুটি ক্রিয়াকলাপের সংমিশ্রণটি নয়, কারণ অন্য থ্রেড এটিকে আপনার কল ContainsKeyএবং এর মধ্যে পরিবর্তন করতে পারে Add

এর অর্থ এই ধরণের দৃশ্যের সঠিকভাবে লিখতে আপনার অভিধানের বাইরের একটি লক প্রয়োজন need

lock (mySafeDictionary)
{
    if (!mySafeDictionary.ContainsKey(someKey))
    {
        mySafeDictionary.Add(someKey, someValue);
    }
}

তবে এখন, আপনাকে বাহ্যিকভাবে লকিং কোডটি লিখতে হচ্ছে দেখে আপনি অভ্যন্তরীণ এবং বাহ্যিক সমন্বয় মেশাচ্ছেন যা সর্বদা অস্পষ্ট কোড এবং ডেডলকসের মতো সমস্যার দিকে পরিচালিত করে। সুতরাং শেষ পর্যন্ত আপনি সম্ভবত ভাল হয়:

  1. Dictionary<TKey, TValue>এটিতে যৌগিক ক্রিয়াকলাপগুলি সংযুক্ত করে, বা একটি বাহ্যিকভাবে সিঙ্ক্রোনাইজ ব্যবহার করুন বা

  2. একটি ভিন্ন ইন্টারফেসের সাথে একটি নতুন থ্রেড-সেফ র‍্যাপার লিখুন (যেমন নয় IDictionary<T>) যা কোনও AddIfNotContainedপদ্ধতির মতো ক্রিয়াকলাপগুলিকে একত্রিত করে যাতে এর থেকে অপারেশনগুলিকে একত্রিত করার দরকার নেই।

(আমি নিজে # 1 নিয়ে যেতে চাই)


10
এটি উল্লেখ করার মতো বিষয় যে .NET 4.0 এর মধ্যে থ্রেড-সেফ কনটেইনারগুলি যেমন সংগ্রহ এবং অভিধানগুলির পুরো গুচ্ছ অন্তর্ভুক্ত থাকবে, যার মানক সংগ্রহের আলাদা ইন্টারফেস রয়েছে (উদাহরণস্বরূপ তারা আপনার জন্য বিকল্প 2 করছেন)।
গ্রেগ বিচ

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

6

কোনও সম্পত্তি দিয়ে আপনার ব্যক্তিগত লক অবজেক্টটি প্রকাশ করা উচিত নয়। ল্যান্ড অবজেক্টটি একটি নিখরচায় পয়েন্ট হিসাবে অভিনয় করার একমাত্র উদ্দেশ্যে ব্যক্তিগতভাবে উপস্থিত থাকতে হবে।

যদি মানক লকটি ব্যবহার করে পারফরম্যান্সটি দুর্বল প্রমাণিত হয় তবে উইনটেলিটকের পাওয়ার থ্রেডিং সংগ্রহের তালা খুব দরকারী be


5

আপনি বর্ণনা করছেন বাস্তবায়ন পদ্ধতিতে বেশ কয়েকটি সমস্যা রয়েছে।

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

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


3

আপনার কনজিউমার অবজেক্টে আপনাকে সিঙ্করুট সম্পত্তি লক করতে হবে না। অভিধানের পদ্ধতিগুলির মধ্যে আপনার যে লক রয়েছে তা যথেষ্ট।

বিস্তারিতভাবে বলার জন্য: যা ঘটছে তা হ'ল আপনার অভিধানটি প্রয়োজনের তুলনায় দীর্ঘ সময়ের জন্য লক করা আছে।

আপনার ক্ষেত্রে যা ঘটে তা নিম্নলিখিত:

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

আপনার SharedDedia ক্লাস অ্যাড পদ্ধতির মধ্যে লক স্টেটমেন্টটি একই প্রভাব ফেলবে আপনি কেবল কোনও গ্রাহককে অ্যাড পদ্ধতিতে কল করার অনুমতি দিতে পারবেন। এই সময়ে, আপনার কাছে রিডান্ট্যান্ট লকিং রয়েছে। আপনি যদি অভিধান অভিধানে দুটি ক্রিয়াকলাপ চালিয়ে যান যা ক্রমাগতভাবে ঘটতে পারে তার নিশ্চয়তা থাকা দরকার হলে আপনি অভিধান অভিধানগুলির মধ্যে একটির বাইরে কেবল সিঙ্করুটকে লক করে দিতেন।


1
সত্য নয় ... আপনি যদি অভ্যন্তরীণভাবে থ্রেড-নিরাপদ এর পরে দুটি অপারেশন করেন তবে এর অর্থ এই নয় যে সামগ্রিক কোড ব্লকটি থ্রেড নিরাপদ। উদাহরণস্বরূপ: যদি (! MyDict.ContainsKey (someKey)) {myDict.Add (someKey, someValue); thread থ্রেডসেফ হবে না, এমনকি এটিতে থাকা কী এবং অ্যাড হ'ল থ্রেডসেফ
টিম

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

আমি অনুমান করি যে তিনি সর্বদা যা করছেন তিনি অভিধানে যুক্ত করছেন কিনা, তবে যেহেতু তার কাছে "// আরও আইডিকোরিয় সদস্য ..." রয়েছে, তাই আমি ধরে নিচ্ছি যে কোনও এক সময় তিনি অভিধান থেকে ডেটাও আবার পড়তে চাইছেন। যদি এটি হয় তবে কিছু বাহ্যিকভাবে অ্যাক্সেসযোগ্য লকিং ব্যবস্থা থাকা দরকার। এটি অভিধানে নিজেই সিঙ্করুট হয় বা অন্য কোনও অবজেক্ট সম্পূর্ণভাবে লকিংয়ের জন্য ব্যবহৃত হয় তা বিবেচ্য নয়, তবে এই জাতীয় পরিকল্পনা ছাড়া সামগ্রিক কোড থ্রেড-সেফ হবে না।
টিম

বাহ্যিক লকিংয়ের প্রক্রিয়াটি যেমন তিনি প্রশ্নের উদাহরণে দেখান: লক (এম_মাইশার্ডড অভিধান.সাইনক্রুট) {এম_মাইশারেডডেরিও.এড করুন (...); } - নিম্নলিখিতগুলি করা পুরোপুরি নিরাপদ হবে: লক (m_MySharedD অভিধান.SyncRoot) {যদি (! m_MySharedD অভিধান.Contains (...)) {m_MySharedD অভিধান.Add (...); Words words অন্য কথায়, বাহ্যিক লকিং প্রক্রিয়া হ'ল লক স্টেটমেন্ট যা সর্বজনীন সম্পত্তি সিঙ্ক্রুটকে পরিচালনা করে।
পিটার মায়ার

0

কেবল একটি চিন্তা কেন অভিধানটি পুনরায় তৈরি করবেন না? পড়া যদি প্রচুর লেখার হয় তবে লকিং সমস্ত অনুরোধকে সিঙ্ক্রোনাইজ করবে।

উদাহরণ

    private static readonly object Lock = new object();
    private static Dictionary<string, string> _dict = new Dictionary<string, string>();

    private string Fetch(string key)
    {
        lock (Lock)
        {
            string returnValue;
            if (_dict.TryGetValue(key, out returnValue))
                return returnValue;

            returnValue = "find the new value";
            _dict = new Dictionary<string, string>(_dict) { { key, returnValue } };

            return returnValue;
        }
    }

    public string GetValue(key)
    {
        string returnValue;

        return _dict.TryGetValue(key, out returnValue)? returnValue : Fetch(key);
    }

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