كما ذكرنا سابقًا، كل مؤشر ترابط له موارده الخاصة، ولكن منطقة التعليمات البرمجية مشتركة، أي أن كل مؤشر ترابط يمكنه تنفيذ نفس الوظيفة. المشكلة التي قد يسببها ذلك هي أن عدة سلاسل تنفذ وظيفة في نفس الوقت، مما يتسبب في ارتباك البيانات ونتائج غير متوقعة، لذلك يجب علينا تجنب هذا الموقف.
يوفر C# قفلًا للكلمات الرئيسية، والذي يمكنه تحديد قسم من التعليمات البرمجية كقسم حصري متبادل (قسم حرج) يسمح القسم الحصري المتبادل لخيط واحد فقط بالدخول في التنفيذ في كل مرة، بينما يجب على سلاسل الرسائل الأخرى الانتظار. في C#، يتم تعريف قفل الكلمة الرئيسية على النحو التالي:
قفل (تعبير) البيان_block
يمثل التعبير الكائن الذي ترغب في تتبعه، وعادةً ما يكون مرجعًا للكائن.
البيان_block هو رمز القسم المتبادل، ولا يمكن تنفيذ هذا الرمز إلا بواسطة مؤشر ترابط واحد في المرة الواحدة.
فيما يلي مثال نموذجي لاستخدام الكلمة الأساسية للقفل. تم شرح استخدام الكلمة الأساسية للقفل والغرض منها في التعليقات.
الأمثلة هي كما يلي:
شفرة
باستخدام النظام؛
باستخدام System.Threading؛
مساحة الاسم ThreadSimple
{
حساب الطبقة الداخلية
{
التوازن الدولي.
Random r = new Random();
الحساب الداخلي (int الأولي)
{
الرصيد = الأولي؛
}
السحب الداخلي (المبلغ الداخلي)
{
إذا (الرصيد <0)
{
// إذا كان الرصيد أقل من 0، فقم بطرح استثناء
رمي استثناء جديد("الرصيد السلبي");
}
// يتم ضمان اكتمال التعليمات البرمجية التالية قبل أن يقوم الخيط الحالي بتعديل قيمة التوازن.
// لن يقوم أي مؤشر ترابط آخر بتنفيذ هذا الرمز لتعديل قيمة التوازن
// لذلك لا يمكن أن تكون قيمة الرصيد أقل من 0
قفل (هذا)
{
Console.WriteLine("الموضوع الحالي:"+Thread.CurrentThread.Name);
// إذا لم تكن هناك حماية للكلمة الأساسية للقفل، فقد يكون ذلك بعد تنفيذ الحكم الشرطي.
// تم تنفيذ مؤشر ترابط آخر Balance=balance-amount وتعديل قيمة الرصيد.
// هذا التعديل غير مرئي لهذا الموضوع، لذلك قد يتسبب في عدم الاحتفاظ بشرط if.
// ومع ذلك، يستمر هذا الموضوع في تنفيذ الرصيد = مبلغ الرصيد، لذلك قد يكون الرصيد أقل من 0
إذا (الرصيد >= المبلغ)
{
Thread.Sleep(5);
الرصيد = الرصيد - المبلغ؛
مبلغ الإرجاع
}
آخر
{
العودة 0؛ // تم رفض المعاملة
}
}
}
الفراغ الداخلي DoTransactions ()
{
لـ (int i = 0; i < 100; i++)
سحب(r.Next(-50, 100));
}
}
اختبار الطبقة الداخلية
{
خيط داخلي ثابت[] threads = new Thread[10];
الفراغ العام الثابت الرئيسي ()
{
حساب الحساب = حساب جديد (0)؛
لـ (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
المواضيع [i] = ر؛
}
لـ (int i = 0; i < 10; i++)
Threads[i].Name=i.ToString();
لـ (int i = 0; i < 10; i++)
المواضيع[i].Start();
Console.ReadLine();
}
}
}فئة المراقبة تقوم بتأمين الكائن
عند مشاركة عدة سلاسل في كائن ما، ستحدث أيضًا مشكلات مشابهة للتعليمات البرمجية الشائعة، ولا ينبغي استخدام الكلمة الأساسية للقفل هنا. توفر الشاشة حلاً لسلاسل الرسائل لمشاركة الموارد.
يمكن لفئة المراقبة قفل كائن ما، ولا يمكن أن يعمل الخيط على الكائن إلا إذا حصل على القفل. تضمن آلية قفل الكائن أن مؤشر ترابط واحد فقط يمكنه الوصول إلى هذا الكائن في المرة الواحدة في المواقف التي قد تسبب الفوضى. يجب أن ترتبط الشاشة بكائن معين، ولكن نظرًا لأنها فئة ثابتة، فلا يمكن استخدامها لتعريف الكائنات، وجميع أساليبها ثابتة ولا يمكن الرجوع إليها باستخدام الكائنات. يوضح التعليمة البرمجية التالية استخدام Monitor لقفل كائن:
...
قائمة الانتظار oQueue=new Queue();
...
Monitor.Enter(oQueue);
......// الآن لا يمكن معالجة كائن oQueue إلا من خلال مؤشر الترابط الحالي
Monitor.Exit(oQueue);// حرر القفل
كما هو موضح أعلاه، عندما يستدعي مؤشر ترابط أسلوب Monitor.Enter() لقفل كائن، فهذا الكائن مملوك له. إذا أرادت سلاسل رسائل أخرى الوصول إلى هذا الكائن، فيمكنها فقط الانتظار حتى يستخدم Monitor.Exit(). طريقة تحرير القفل. من أجل التأكد من أن مؤشر الترابط سيحرر القفل في النهاية، يمكنك كتابة طريقة Monitor.Exit() في كتلة التعليمات البرمجية النهائية في بنية حاول الالتقاط أخيرًا.
بالنسبة لأي كائن مقفل بواسطة جهاز المراقبة، يتم تخزين بعض المعلومات المتعلقة به في الذاكرة:
الأول هو إشارة إلى الخيط الذي يحمل القفل حاليًا؛
والثاني هو قائمة انتظار التحضير، والتي تخزن الخيوط الجاهزة للحصول على الأقفال؛
والثالث هو قائمة انتظار الانتظار، والتي تحتوي على إشارة إلى قائمة الانتظار التي تنتظر حاليًا تغيير حالة الكائن.
عندما يكون الخيط الذي يمتلك قفل الكائن جاهزًا لتحرير القفل، فإنه يستخدم طريقة Monitor.Pulse () لإعلام الخيط الأول في قائمة انتظار الانتظار، لذلك يتم نقل الخيط إلى قائمة انتظار التحضير عند تحرير قفل الكائن ، في قائمة انتظار التحضير، يمكن للخيط الحصول على قفل الكائن على الفور.
فيما يلي مثال يوضح كيفية استخدام الكلمة الأساسية للقفل وفئة المراقبة لتنفيذ مزامنة سلسلة الرسائل والاتصال بها، وهي أيضًا مشكلة نموذجية للمنتج والمستهلك.
في هذا الروتين، يتناوب خيط المنتج وخيط المستهلك، ويكتب المنتج رقمًا، ويقرأه المستهلك على الفور ويعرضه (يتم تقديم جوهر البرنامج في التعليقات).
مساحات أسماء النظام المستخدمة هي كما يلي:
باستخدام النظام؛
باستخدام System.Threading؛
أولاً، قم بتعريف فئة الخلية للكائن الذي يتم التشغيل عليه، هناك طريقتان في هذه الفئة: ReadFromCell() وWriteToCell. سوف يستدعي مؤشر ترابط المستهلك ReadFromCell() لقراءة محتويات cellContents وعرضها، وستستدعي عملية المنتج طريقة WriteToCell() لكتابة البيانات إلى cellContents.
الأمثلة هي كما يلي:
شفرة
خلية الطبقة العامة
{
int cellContents; // محتويات كائن الخلية
bool ReaderFlag = false; // علامة الحالة، عندما تكون صحيحة، يمكن قراءتها، وعندما تكون خاطئة، يمكن كتابتها
القراءة من الخلية العامة ()
{
lock(this) // ما الذي تضمنه الكلمة الرئيسية للقفل؟ يرجى قراءة المقدمة السابقة للقفل.
{
if (!readerFlag)//إذا لم يكن قابلاً للقراءة الآن
{
يحاول
{
// انتظر حتى يتم استدعاء طريقة Monitor.Pulse () في طريقة WriteToCell
Monitor.Wait(this);
}
قبض على (SynchronizationLockException ه)
{
Console.WriteLine(e);
}
قبض على (ThreadInterruptedException ه)
{
Console.WriteLine(e);
}
}
Console.WriteLine("استهلاك: {0}"،cellContents);
readFlag = false;
// أعد تعيين علامة readFlag للإشارة إلى اكتمال سلوك الاستهلاك
Monitor.Pulse(this);
// إعلام طريقة WriteToCell () (يتم تنفيذ هذه الطريقة في مؤشر ترابط آخر وهي في انتظار)
}
عودة محتويات الخلية؛
}
الفراغ العام WriteToCell(int n)
{
قفل (هذا)
{
إذا (علم القارئ)
{
يحاول
{
Monitor.Wait(this);
}
قبض على (SynchronizationLockException ه)
{
// عند استدعاء الأساليب المتزامنة (بالإشارة إلى أساليب فئة المراقبة باستثناء Enter) في مناطق التعليمات البرمجية غير المتزامنة
Console.WriteLine(e);
}
قبض على (ThreadInterruptedException ه)
{
// إحباط عندما يكون الخيط في حالة انتظار
Console.WriteLine(e);
}
}
محتويات الخلية = ن؛
Console.WriteLine("إنتاج: {0}"،cellContents);
readFlag = true;
Monitor.Pulse(this);
// قم بإعلام طريقة ReadFromCell () المنتظرة في موضوع آخر
}
}
}
يتم تعريف فئة المنتج CellProd وفئة المستهلك CellCons أدناه، وكلاهما لهما طريقة واحدة فقط ThreadRun()، بحيث يكون كائن وكيل ThreadStart المقدم إلى الخيط في الوظيفة Main() بمثابة مدخل إلى الخيط.
الفئة العامة CellProd
{
خلية الخلية؛ // كائن الخلية الذي يتم تشغيله
intquity = 1; // عدد المرات التي ينتج فيها المنتج، مهيئًا بـ 1
CellProd العام (صندوق الخلية، طلب int)
{
//المنشئ
خلية = مربع؛
الكمية = الطلب؛
}
ThreadRun الفراغ العام ()
{
ل(int وبير = 1؛ وبير <= الكمية؛ وبير ++)
cell.WriteToCell(looper); // يكتب المنتج المعلومات إلى كائن العملية
}
}
الفئة العامة CellCons
{
خلية خلوية؛
كمية كثافة العمليات = 1؛
CellCons العامة (صندوق الخلية، طلب int)
{
//المنشئ
خلية = مربع؛
الكمية = الطلب؛
}
ThreadRun الفراغ العام ()
{
int valReturned;
ل(int وبير = 1؛ وبير <= الكمية؛ وبير ++)
valReturned=cell.ReadFromCell();// يقرأ المستهلك المعلومات من كائن العملية
}
}
ثم في الدالة Main() لفئة MonitorSample أدناه، ما يتعين علينا القيام به هو إنشاء خيطين كمنتجين ومستهلكين، واستخدام طريقة CellProd.ThreadRun() وطريقة CellCons.ThreadRun() لتنفيذ العمليات على نفس الشيء كائن الخلية.
شفرة
نموذج مراقبة الطبقة العامة
{
الفراغ العام الثابت الرئيسي (سلسلة [] الحجج)
{
int result = 0; // بت العلم إذا كان 0، فهذا يعني أنه لا يوجد خطأ في البرنامج.
خلية الخلية = خلية جديدة ()؛
// يستخدم ما يلي الخلية لتهيئة فئتي CellProd وCellCons. أوقات الإنتاج والاستهلاك هي 20 مرة.
CellProd prod = new CellProd(cell, 20);
سلبيات CellCons = جديد CellCons(cell, 20);
منتج الموضوع = new Thread(new ThreadStart(prod.ThreadRun));
مستهلك الموضوع = new Thread(new ThreadStart(cons.ThreadRun));
// تم إنشاء كل من خيط المنتج وخيط المستهلك، ولكن لم يبدأ التنفيذ.
يحاول
{
منتج.ابدأ ()؛
المستهلك.ابدأ()؛
منتج.انضمام () ؛
Consumer.Join() ;
Console.ReadLine();
}
قبض على (ThreadStateException ه)
{
// عندما يتعذر على مؤشر الترابط تنفيذ العملية المطلوبة بسبب حالته
Console.WriteLine(e);
النتيجة = 1؛
}
قبض على (ThreadInterruptedException ه)
{
// إحباط عندما يكون الخيط في حالة انتظار
Console.WriteLine(e);
النتيجة = 1؛
}
// على الرغم من أن الدالة Main() لا تُرجع قيمة، إلا أن العبارة التالية يمكنها إرجاع نتيجة التنفيذ إلى العملية الأصلية
Environment.ExitCode = result;
}
}
في الروتين أعلاه، تتم المزامنة عن طريق انتظار Monitor.Pulse(). أولا، ينتج المنتج قيمة، وفي نفس الوقت يكون المستهلك في حالة انتظار حتى يتلقى "نبضة" من المنتج لإعلامه بانتهاء الإنتاج، وبعد ذلك يدخل المستهلك في حالة الاستهلاك. ويبدأ المنتج في انتظار انتهاء المستهلك، وسيتم استدعاء "النبض" المنبعث من Monitor.Pulese() بعد العملية.
نتيجة تنفيذه بسيطة:
إنتاج: 1
الاستهلاك: 1
الإنتاج: 2
الاستهلاك: 2
الإنتاج: 3
الاستهلاك: 3
...
...
الإنتاج: 20
الاستهلاك: 20
في الواقع، ساعدنا هذا المثال البسيط في حل المشكلات الكبيرة التي قد تنشأ في التطبيقات متعددة الخيوط، وطالما أنك تفهم الطرق الأساسية لحل التعارضات بين الخيوط، فمن السهل تطبيقها على برامج أكثر تعقيدًا.
-