-
Notifications
You must be signed in to change notification settings - Fork 26
异步&多线程-桂昕-无34 #7
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
HW-Async&Thread-1/Program.cs
Outdated
*/ | ||
SetParent(A, this); | ||
SetParent(B, this); | ||
val = ExprFunc(ExprA.Val, ExprB.Val); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
显然,由于被包含在构造函数中,这一段计算将会同步化,这并不符合题意。
注意,这一题最重要的要求就是异步计算,计算结果究竟是否正确其实还在其次。
HW-Async&Thread-1/Program.cs
Outdated
|
||
public void WaitForAvailable() | ||
{ | ||
while (!Available) ; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个忙等待虽然可行,但是大量使用可能会使你的电脑发烫(
你可以尝试使用 Monitor.Wait
和 Monitor.Pulse
来避免忙等待。
HW-Async&Thread-1/Program.cs
Outdated
lock (lockVal) | ||
{ | ||
Unavilablize(); | ||
val = ExprFunc(ExprA.Val, ExprB.Val); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这个位置,其本身是没有问题的;但是结合锁的使用来看,问题很大。
我们可以认为,计算所需要的时间是很久的;甚至于从开始更新到即将读取之时,程序都无法完成计算任务。对于这种情况,我们应当认为我们确实是在数据尚未更新时就想要读取,那么就有了 WaitForAvailable
;如果用户不使用 WaitForAvailable
,那么就不保证值的正确性。
但是,你现在将整个计算过程置于锁 lockVal
之中,由于开始更新在前,在一定的可能下 (如主线程中间也进行了一些微小的处理),使得将要读取时锁被计算任务控制,那么将会一直阻塞直至计算任务的完成 (当然,这个值也不一定就是正确的)。我们只希望锁是用来保护读取行为的;换言之,在这一场景中,锁只应当用于资源互斥而非条件同步。
一个示例,随机地输出 0 0
、0 21
、21 49
:
ValueExpr a = new(1);
ValueExpr b = new(2);
ValueExpr c = new(3);
ValueExpr d = new(4);
AddExpr add1 = new(a, b);
AddExpr add2 = new(c, d);
MulExpr mul = new(add1, add2);
//mul.WaitForAvailable();
Console.WriteLine(mul.Val);
a.NewVal = 5;
//mul.WaitForAvailable();
Console.WriteLine(mul.Val);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
还有一个问题,是关于用时的。
如果使用原先的阻塞式构造函数,整个构造过程耗费3T (T为单次计算用时),而后面第二次等待耗费2T,这很符合预期。
但是如果采用非阻塞式构造函数,构造过程几乎不耗费用时;第一次等待耗费2T或3T,随后第一次读取时等待锁2T或1T,也就是两段总共4T;第二次等待耗费2T,然后读取不需要等待锁。
由于这里必须采用非阻塞的构造函数,因此上述的奇怪现象引出一个问题——WaitForAvailable
或者锁 lockVal
有着潜在的问题,需要用户等待两次才能读取到值。我没看出来问题在哪,但显然这个用时是无论如何不正确的。
/*
* 直接对parent进行修改,而不是调用Register方法
* Register操作不可能对子树造成影响,只会对父节点及以上的节点值造成影响
* 而此处构造函数中已经进行了计算,使用Register造成了重复计算,白白浪费两倍时间
*/ 这段思考很好。 /// <summary>
/// 结点值是否可用,用于判断结点值是否已经更新完毕
/// </summary>
/// <value></value>
public bool Available { get; protected set; } = true; 增加 将 第二题基本满足要求。 |
修改了第一题,但是有千分之几的概率锁在Wait处,实在调不动了:( |
第一题和第二题