线程、同步与锁——Mutex想说爱你不容易

来源:互联网 发布:学生值得买的东西知乎 编辑:程序博客网 时间:2024/06/09 18:58

除了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();

            

        }

}

恩,Thread1Mutex.WaitOne()后,就想到与Thread1拿到了Mutex所有权,这时Thread2得到了同样的Mutex,然后Mutex.WaitOne(),也想拿到Mutex的所有权,这时就必须等待了。这里只需要两点就能明白什么是Mutex了:

1.   Mutex是一个令牌,当一个线程拿到这个令牌时运行,另外想拿到令牌的线程就必须等待,直到拿到令牌的线程释放令牌。没有所有权的线程是无法释放令牌的。

2.   Mutexfalsestring)中的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 

2Mutex的生命周期,这个问题让我郁闷了很久,因为不太了解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访问对象和访问文件使用的是同样的安全机制(虽然我还没看懂)。

原创粉丝点击