除了Lock()、Monitor之外,我们最长用的就是Mutex了,但是玩不好Mutex就总会造成死锁或者AbandonedMutexException(我就玩的不怎么好,在并发性访问测试的时候总是遇到关于Mutex的问题,各位线虫见笑了,不过还是把我遇到的一些问题和总结拿出来和大家分享,有误的地方还往指正。
除了Lock()、Monitor之外,我们最长用的就是Mutex了,但是玩不好Mutex就总会造成死锁或者AbandonedMutexException(我就玩的不怎么好,在并发性访问测试的时候总是遇到关于Mutex的问题,各位线虫见笑了,不过还是把我遇到的一些问题和总结拿出来和大家分享,有误的地方还往指正。
还是先举一个简单的例子,来说明一下这个东西:
public class ThreadMutex
{
public void Test()
{
Thread t1 = new Thread(Thread1);
Thread t2 = new Thread(Thread2);
t1.Start();
t2.Start();
}
public void Thread1()
{
Mutex m = new Mutex(false, "test");
bool b2 = m.WaitOne();
Console.WriteLine("Thread1 get the mutex : " + b2);
Thread.Sleep(10000);
m.ReleaseMutex();
}
public void Thread2()
{
Mutex m = new Mutex(false, "test");
bool b2 = m.WaitOne();
Console.WriteLine("Thread2 get the mutex : " + b2);
Thread.Sleep(1000);
m.ReleaseMutex();
}
}
恩,Thread1中Mutex.WaitOne()后,就想到与Thread1拿到了Mutex所有权,这时Thread2得到了同样的Mutex,然后Mutex.WaitOne(),也想拿到Mutex的所有权,这时就必须等待了。这里只需要两点就能明白什么是Mutex了:
1. Mutex是一个令牌,当一个线程拿到这个令牌时运行,另外想拿到令牌的线程就必须等待,直到拿到令牌的线程释放令牌。没有所有权的线程是无法释放令牌的。
2. Mutex(false,”string”)中的string是令牌的关键,或者可以叫令牌名,因为Mutex是跨进程的,整个系统中只会有唯一的令牌存在所以,也就是说你在一个应用程序中的一个线程中得到了Mutex的所有权,那在另外一个线程中的另外的线程想得到他就必须要等待。
要弄清楚Mutex就还需要弄清楚两个很重要的问题:
1.那就是Mutex是调用的Win32 的API
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
BOOL bInitialOwner,
LPCTSTR lpName
);
这就是他为什么能跨进程访问的原因,正是由于它使用P/Invoke,他的效率问题就凸现出来,明显不如Monitor之类的快,用的时候还需多多斟酌。
下面放一个Mutex的简单实现,看看Mutex在.net下是如何实现的。
1 using System;
2
3 using System.Runtime.InteropServices;
4
5 using System.Threading;
6
7
8
9 public class NativeMutex : WaitHandle
10
11 {
12
13 private uint mutexHandle;
14
15 private static bool newlyCreated;
16
17
18
19 public NativeMutex() : this(false, String.Empty, out newlyCreated) { }
20
21 public NativeMutex(bool initiallyOwned) : this(initiallyOwned, String.Empty, out newlyCreated) { }
22
23 public NativeMutex(bool initiallyOwned, string name) : this(initiallyOwned, name, out newlyCreated) { }
24
25
26
27 public NativeMutex(bool initiallyOwned, string name, out bool createdNew)
28
29 {
30
31 this.mutexHandle = NativeMethods.CreateNativeMutex(initiallyOwned, name, out createdNew);
32
33 newlyCreated = createdNew;
34
35 this.Handle = (IntPtr)this.mutexHandle;
36
37 }
38
39
40
41 public void ReleaseNativeMutex()
42
43 {
44
45 NativeMethods.ReleaseNativeMutex(this.mutexHandle);
46
47 this.Close();
48
49 }
50
51 }
52
53
54
55 internal class NativeMethods
56
57 {
58
59 [DllImport("kernel32.dll", SetLastError = true)]
60
61 private static extern UInt32 CreateMutex(
62
63 ref SECURITY_ATTRIBUTES
64
65 SecurityAttributes,
66
67 [MarshalAs(UnmanagedType.Bool)] bool InitialOwner,
68
69 string MutexName);
70
71
72
73 [DllImport("kernel32.dll", SetLastError = true)]
74
75 [return: MarshalAs(UnmanagedType.Bool)]
76
77 private static extern bool ReleaseMutex(UInt32 hMutex);
78
79
80
81 [DllImport("Advapi32.dll", SetLastError = true)]
82
83 [return: MarshalAs(UnmanagedType.Bool)]
84
85 private static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(
86
87 string StringSecurityDescriptor,
88
89 UInt32 StringSDRevision,
90
91 ref int SecurityDescriptor,
92
93 ref int SecurityDescriptorSize);
94
95
96
97 [DllImport("kernel32.dll", SetLastError = true)]
98
99 private static extern UInt32 LocalFree(UInt32 hMem);
100
101
102
103 [StructLayoutAttribute(LayoutKind.Sequential)]
104
105 private struct SECURITY_ATTRIBUTES
106
107 {
108
109 internal uint dwSize;
110
111 internal int lpSecurityDescriptor;
112
113 internal bool bInheritHandle;
114
115 }
116
117
118
119 private NativeMethods() { }
120
121
122
123 internal static uint CreateNativeMutex(bool initiallyOwned, string mutexName, out bool createdNew)
124
125 {
126
127 const uint SDDL_REVISION_1 = 1;
128
129 const int ERROR_ALREADY_EXISTS = 183;
130
131
132
133 uint newMutex = 0;
134
135 createdNew = false;
136
137 SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
138
139
140
141 try
142
143 {
144
145 sa.dwSize = (UInt32)System.Runtime.InteropServices.Marshal.SizeOf(sa);
146
147 sa.bInheritHandle = true;
148
149 sa.lpSecurityDescriptor = 0;
150
151 int securityDescriptorSize = 0;
152
153
154
155 ConvertStringSecurityDescriptorToSecurityDescriptor(
156
157 "D:(A;NP;0x001f0001;;;WD)",
158
159 SDDL_REVISION_1,
160
161 ref sa.lpSecurityDescriptor,
162
163 ref securityDescriptorSize);
164
165 int lastError = Marshal.GetLastWin32Error();
166
167
168
169 if (lastError != 0)
170
171 {
172
173 throw new Exception(
174
175 "Error creating security descriptor: " + lastError);
176
177 }
178
179
180
181 newMutex = CreateMutex(ref sa, initiallyOwned, mutexName);
182
183 lastError = Marshal.GetLastWin32Error();
184
185
186
187 if (newMutex != 0)
188
189 {
190
191 if (lastError != ERROR_ALREADY_EXISTS)
192
193 {
194
195 createdNew = true;
196
197 }
198
199 }
200
201 else
202
203 {
204
205 throw new Exception(
206
207 "Error creating new mutex: " + lastError);
208
209 }
210
211 }
212
213 finally
214
215 {
216
217 if (sa.lpSecurityDescriptor != 0)
218
219 {
220
221 LocalFree((UInt32)sa.lpSecurityDescriptor);
222
223 }
224
225 }
226
227 return newMutex;
228
229 }
230
231
232
233 internal static void ReleaseNativeMutex(uint mutex)
234
235 {
236
237 if (mutex != 0)
238
239 {
240
241 bool ret = ReleaseMutex(mutex);
242
243 int lastError = Marshal.GetLastWin32Error();
244
245 if (ret == false)
246
247 {
248
249 throw new Exception(
250
251 "Error releasing mutex: " + lastError);
252
253 }
254
255 }
256
257 }
258
259 }
260
261
2.Mutex的生命周期,这个问题让我郁闷了很久,因为不太了解Mutex的机制,使得我也没法弄清楚到底能活多长时间,这也是AbandonedMutexException经常会出现的原因。还是先来看一段程序:
public class ThreadMutex
{
public void Test()
{
Thread t1 = new Thread(Thread1);
Thread t2 = new Thread(Thread2);
t1.Start();
t2.Start();
}
public void Thread1()
{
Mutex m = new Mutex(false, "test");
bool b2 = m.WaitOne();
Console.WriteLine("Thread1 get the mutex : " + b2);
}
public void Thread2()
{
Thread.Sleep(10);//保证Thread1执行完
Mutex m = new Mutex(false, "test");
bool b2=m.WaitOne();
Console.WriteLine(b2);
m.ReleaseMutex();
}
}
在Thread2中的WaitOne()方法就会报错了,AbandonedMutexException,原因就是Thread1拿到了Mutex后没有释放,Thread1就结束了,这样Mutex成了被抛弃的地孩子了,呵呵。但是如果垃圾收集了,就不一样咯。代码稍微修改了一下:
public class ThreadMutex
{
public void Test()
{
Thread t1 = new Thread(Thread1);
Thread t2 = new Thread(Thread2);
t1.Start();
t2.Start();
}
public void Thread1()
{
Mutex m = new Mutex(false, "test");
bool b2 = m.WaitOne();
Console.WriteLine("Thread1 get the mutex : " + b2);
}
public void Thread2()
{
Thread.Sleep(10);//保证Thread1执行完
GC.Collect();
GC.WaitForPendingFinalizers();
bool b1;
Mutex m = new Mutex(false, "test",out b1);
Console.WriteLine(b1);
bool b2=m.WaitOne();
Console.WriteLine(b2);
m.ReleaseMutex();
}
}
结果是:
Thread1 get the mutex : True
True
True
Thread2里面的Mutex是新创建的,呵呵,这里面的玄妙自己体会吧。
最后要说一下的是Mutex的访问和window访问文件的机制基本上是一样的,window访问对象和访问文件使用的是同样的安全机制(虽然我还没看懂)。