עבור לתוכן

C#- Monitor.wait/Pluse

Featured Replies

פורסם

Monitor: ב-Wait אז ברגע שאתה משתמש בוא, הTHREAD נעצר עד שהאובייקט מתפנה ואז הWAIT ישר נועל אותו שוב ומתחיל להשתמש בוא. תקן אותי אם אני טועה.

עכשיו הPLUSE: אותו בעצם אותו דבר בול אחד לאחד כמו EXIT אבל מה שקראתי בMSDN הוא אומר שהוא מודיע רק לאחד הבא שהאובייקט שאני עושה עליו PULSE כבר לא נעול או לחילופין PULESALL שמודיע לכל הTHREADים האחרים שהאובייקט כבר לא נעול.

עכשיו השאלה שלי זה למה לעזזל שאני ירצה להשתמש בזה אם אני כבר עושה EXIT ושאר הTHREADים שיש בהם WAIT יעשו את הסדר ביניהם מי יקבל את הנעילה עכשיו. אני מקווה שהבנת אותי ואם לא אני יסביר שוב.

פשוט ביניתי איזה סנקרון קטן פה ושאני מריץ הוא עובד חלק אבל שאני עובר עליו שורה שורה עם DEBUG הוא זורק לי exeption כל פעם.. ממש מוזר:

יש לי מחלקה ליסט שבניתי עכשיו צנזרתי תקוד הלא רלוונתי כי הוא לא קשור בכלל לבעיה. יש לי Amount כדי לבדוק כמה איברים יש לי בליסט. במידה ואני מעדכן את הכמות ב1 אז הוא מגדיל ב1. אם אני משנה אותו ל1- אז הוא קטן ב1 וואם 0 אז הסכום משתנה ל0.

לא מסובך.

אם לא הבנתם הנה הסבר קצר ופשוט:

יצרתי ליסט ורציתי להכניס לו איבירים. כל פעם שאני מכניס איבר לליסט אני מוסיף 1 לAMOUNT.

הבעיה מתחילה שאני מוסיף איברים לליסט בו זמנית, כמו שאתה רואה בMAIN.

אז רשמתי ככה: אם הAMOUNT תפוס (amountLock- זה אובייקט ריק שבעזרתו אני יודע אם מישהו משתמש בAmount ברגע זה ואותו אני נועל.- תייחס לזה כמשתנה בוליאני שבערתו אני יודע אם משתמשים או לא בamount ברגע זה).

נמשיך: אם המשתנה תפוס אז תחכה עד שיתפנה ואז תגדיל את הamount ב-1. שתסיים את זה אז תעיף תנעילה ואת זה אתם יודע למה.




using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
public class List<T>
{
private object _AmountLock;
private int _Amount;
public List()
{
this._AmountLock = new object();
this._Amount = 0;
}
public void Insert(Node<T> ToWhere, T Info)
{
this.Amount = 1;
}
public void InsertLast(T Info)
{
this.Insert(null, Info);
}
public void InsertFirst(T Info)
{
this.Insert(null, Info);
}
public int Amount
{
get
{
return this._Amount;
}
private set
{
string Name = Thread.CurrentThread.Name;
if (!Monitor.TryEnter(this._AmountLock))
Monitor.Wait(this._AmountLock);
if (value == 1)
this._Amount++;
else
if (value == 0)
this._Amount = 0;
else
if (value == -1)
this._Amount--;
Monitor.Exit(this._AmountLock);
}
}
}
}

עכשיו זה הMAIN שלי: אני פשוט מכניס כמה איבירים (בו זמנית!). אחרי זה הוא אמור להראות 2 והוא באמת מראה. אבל שאני עובר עליו בDEBUG הוא קורס. תנסה כמה פעמים. תודה רבה




using System;
using System.Threading;
namespace ConsoleApplication1
{
class CalculateTest
{
static List<int> List = new List<int>();
static void Main()
{
Thread t1 = new Thread(new ThreadStart(a));
Thread t2 = new Thread(new ThreadStart(a));
Thread t3 = new Thread(new ThreadStart(a));
Thread t4 = new Thread(new ThreadStart(a));
Thread t5 = new Thread(new ThreadStart(a));
t1.Name = "1";
t2.Name = "2";
t1.Start();
t2.Start();
Thread.Sleep(500);
Console.WriteLine(List.Amount);
Console.ReadKey();
}
static void a()
{
List.InsertFirst(3);
}
}
}

פורסם

לא נראה לי שהבנת נכון למה בדיוק משמשים Enter/Exit/Wait/Pulse.

Enter משמשת לנעילה. כרגע שמישהו נועל את המנעול באמצעות Enter, כל קריאה אחרת ל-Enter על אותו מנעול תחסם עד שהמנעול ישוחרר. TryEnter היא דומה, רק שהיא אף פעם לא חוסמת (כלומר אם המנעול כרגע תפוס, היא פשוט תחזיר False והת'רד ימשיך לרוץ). בד"כ לא משתמשים בה.

Exit משחררת את המנעול. אם יש ת'רד אחר שכרגע חסום על המנעול הזה (כי הוא קרא ל-Enter) אז הוא ימשיך לרוץ, ומיד ינעל את המנעול בעצמו. אם יש כמה ת'רדים כאלה, אז כמובן רק אחד מהם ימשיך לרוץ והאחרים ימשיכו לחכות.

שתי הפונקציות הללו משמשות ל-Mutual Exclusion (בקיצור Mutex), כלומר מניעה משני ת'רדים לרוץ במקביל על קטע קוד מסוים. לדוגמה, אם יש לך אובייקט רשימה ואתה רוצה למנוע משני ת'רדים להכניס ולהוציא איברים מהרשימה בו זמנית.

אם ה-Enter וה-Exit יושבים באותה פונקציה, עדיף להשתמש במילה המיוחדת lock, כי היא כבר כוללת בתוכה את ה-try/finally ופחות מועדת לשגיאות.

את הפונקציות Wait/Pulse/PulseAll ניתן להריץ רק מתוך ת'רד שכרגע כבר נועל את המנעול.

Wait משחררת את המנעול (כמו Exit) ומחכה עד שהוא יקבל Pulse או PulseAll על המנעול. ברגע שזה קורה, הוא אוטומטית עושה לו חזרה Enter. אי אפשר לקרוא ל-Wait בלי שהמנעול נעול ע"י בת'רד שלך.

Pulse מרימה סיגנל לת'רד אחד בלבד שכרגע עושה Wait על המנעול. הת'רד עובר ממצב של Wait למצב של Enter, כלומר הוא מחכה שהמנעול ישתחרר. ברגע שהת'רד שעשה Pulse ישחרר את המנעול, הת'רד שחיכה ינסה להמשיך לרוץ (אם הוא יכול, כי יכול להיות שיש עוד ת'רדים שמחכים למנעול).

PulseAll פועלת כמו Pulse, רק שהיא מרימה סיגנל לכל הת'רדים שכרגע עושים Wait על המנעול. חוץ מזה, Pulse ו-PulseAll זהות.

בשלוש הפונקציות הללו לא משתמשים בשביל Mutex, אלא בשביל סנכרון בין ת'רדים (כלומר, אם ת'רד אחד צריך שת'רד אחר יסיים את העבודה שלו על מנת להמשיך לעבוד). לדוגמה, אם אתה מנהל אובייקט של תור, שת'רד אחד מכניס אליו איברים ות'רד אחר מוציא ממנו איברים, ואתה רוצה שהת'רד שמכניס איברים כל פעם יודיע לת'רד שמוציא מתי יש איברים חדשים. אז זה שמוציא איברים עושה Wait, וברגע שזה שמכניס איברים מכניס איבר חדש, הוא עושה Pulse כדי להודיע על כך.

עכשיו נגיע לבעיה בקוד שלך - אתה עושה TryEnter ורק אם הוא נכשל אתה עושה Wait, כלומר אתה מחכה על אובייקט שלא נעול אצלך. חוץ מזה, אתה עושה Wait בלי לעשות Pulse בשום מקום, מה שיוביל לתקיעה.

נ.ב. לא כל כך ברור לי השימוש שלך ב-Amount. הייתי מצפה שאם אני עושה Amount = 1 ואז x = Amount אז x יכיל 1, ולא משהו אחר.

פורסם
  • מחבר

ממש תודה רבה! עזרת לי להבין את המחלקה בברור והבנתי איפה טעיתי והקוד עובד חלק עכשיו! תודה רבה!

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים