msdn中对于Mutex的解释是:可用于进程间同步的同步基元,顾名思义也就是可用于进程中的同步,并且c#本质论中也提出了Mutex可以用于同步对文件或者其它跨进程资源的访问,下面就有几个疑问?
1、Mutex能不能用于同一进程下的不同线程的同步?
答:Mutex可以用于同一进程下不同线程的同步。
1)
private static int count=0;
public Form1()
{
InitializeComponent();
Task task1 = Task.Run(()=> Increase());
Task task2 = Task.Run(()=>Decrease());
Task.WaitAll (new Task[] { task1,task2});
Console.WriteLine(count);
}
private static void Increase()
{
for(int i=0;i<int.MaxValue;i++)
{
count++;
}
}
private static void Decrease()
{
for(int i=0;i<int.MaxValue;i++)
{
count--;
}
}
最终控制台输出的结果是-456690760,而非我们想象的0,这就是由于多个线程访问相同的数据造成了竞态条件导致,下面我们使用Mutex来实现对count变量的同步,如下:
private static int count = 0;
Mutex mutex = new Mutex(false );
public Form1()
{
InitializeComponent();
Task task1 = Task.Run(() => Increase());
Task task2 = Task.Run(() => Decrease());
Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine(count);
}
private void Increase()
{
mutex.WaitOne();//申请互斥体
for (int i = 0; i < int.MaxValue; i++)
{
count++;
}
mutex.ReleaseMutex();
}
private void Decrease()
{
mutex.WaitOne();
for (int i = 0; i < int.MaxValue; i++)
{
count--;
}
mutex.ReleaseMutex();
}
2、Mutex在跨进程中怎么使用?
下面举两个例子说明Mutex在跨进程中的使用;
1)防止应用程序被打开两次;
static void Main()
{
bool createNew;
Mutex mt = new Mutex(true, “ApplicationMutex”, out createNew);
if (createNew)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
MessageBox.Show("程序已经打开!");
Process.GetCurrentProcess().Kill();
}
}
上述代码中就是利用Mutex构造函数的第三个参数来防止程序被多次打开,Msdn的解释是:
如果创建了局部互斥体(即,如果 name 为 null 或空字符串)或指定的命名系统互斥体,则createdNew 返回true;如果指定的命名系统互斥体已存在,则为 createdNew 返回false。
2)Mutex在多个进程中访问文件时实现同步;
多进程不同步对文件的访问时:
System.IO.IOException:“文件“d:\TestFile\1.txt”正由另一进程使用,因此该进程无法访问此文件。”
进程一如下:
public partial class Form1 : Form
{
Mutex mutex = new Mutex(false, "MyMutex");
public Form1()
{
InitializeComponent();
Thread thread = new Thread(WriteFile);
thread.IsBackground = true;
thread.Start("d:\\TestFile\\1.txt");
}
private void WriteFile(object fileName)
{
while (true)
{
string TempfileName = fileName.ToString();
//mutex.WaitOne();
StreamWriter sw = new StreamWriter(TempfileName, false, Encoding.GetEncoding("GB2312"));
sw.WriteLine(DateTime.Now.ToString());
sw.Flush();
sw.Close();
//mutex.ReleaseMutex();
Thread.Sleep(10);
}
}
}
进程二如下:
public partial class Form1 : Form
{
Mutex mutex = new Mutex(false, "MyMutex");
public Form1()
{
InitializeComponent();
Thread thread = new Thread(ReadFile);
thread.IsBackground = true;
thread.Start("d:\\TestFile\\1.txt");
}
private void ReadFile(object fileName)
{
while(true)
{
string tempFileName = fileName.ToString();
string str;
//mutex.WaitOne();
StreamReader sr = new StreamReader(tempFileName, Encoding.GetEncoding("GB2312"));
str = sr.ReadLine();
Thread.Sleep(10);
sr.Close();
//mutex.ReleaseMutex();
this.Invoke(new Action (() => label1.Text = str));
}
}
}
同时运行这个两个进程后,就会报出文件被另一进程占用的异常。
多进程同步对文件的访问:
进程一如下:
public partial class Form1 : Form
{
Mutex mutex = new Mutex(false, "MyMutex");
public Form1()
{
InitializeComponent();
Thread thread = new Thread(WriteFile);
thread.IsBackground = true;
thread.Start("d:\\TestFile\\1.txt");
}
private void WriteFile(object fileName)
{
while (true)
{
string TempfileName = fileName.ToString();
mutex.WaitOne();
StreamWriter sw = new StreamWriter(TempfileName, false, Encoding.GetEncoding("GB2312"));
sw.WriteLine(DateTime.Now.ToString());
sw.Flush();
sw.Close();
mutex.ReleaseMutex();
Thread.Sleep(10);
}
}
}
进程二如下:
public partial class Form1 : Form
{
Mutex mutex = new Mutex(false, "MyMutex");
public Form1()
{
InitializeComponent();
Thread thread = new Thread(ReadFile);
thread.IsBackground = true;
thread.Start("d:\\TestFile\\1.txt");
}
private void ReadFile(object fileName)
{
while (true)
{
string tempFileName = fileName.ToString();
string str;
mutex.WaitOne();
StreamReader sr = new StreamReader(tempFileName, Encoding.GetEncoding("GB2312"));
str = sr.ReadLine();
Thread.Sleep(10);
sr.Close();
mutex.ReleaseMutex();
this.Invoke(new Action(() => label1.Text = str));
}
}
}
3、常见错误及原因分析
1)System.ApplicationException:“从不同步的代码块中调用了对象同步方法
private void Decrease()
{
for (int i = 0; i < int.MaxValue; i++)
{
count--;
}
mutex.ReleaseMutex();
}
private static int count = 0;
Mutex mutex = new Mutex(true);
public Form1()
{
InitializeComponent();
Task task2 = Task.Run(() => Decrease());
Thread.Sleep(10000);
//Task task1 = Task.Run(() => Increase());
//Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine(count);
}
上面的代码就会报这个错误,这是由于Mutex创建对象时,第一个参数为true, Mutex mutex = new Mutex(true);这个true的意思是让创建这个同步对象的线程拥有互斥体,这里创建互斥体的线程是UI线程,但是我们在Task2中调用了mutex.ReleaseMutex()这个同步方法,而Mutex使用时就有一个规定拥有互斥体的线程才可以去释放互斥体,所以出现了上面的错误,如果将代码改成下面这两种情况,则就不会出问题。
情况一、直接在UI线程调用ReleaseMutex
public Form1()
{
InitializeComponent();
Decrease();
//Task task2 = Task.Run(() => Decrease());
Thread.Sleep(10000);
//Task task1 = Task.Run(() => Increase());
//Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine(count);
}
情况二、在task2中创建Mutex的对象
public partial class Form1 : Form
{
private static int count = 0;
public Form1()
{
InitializeComponent();
Task task2 = Task.Run(() => Decrease());
Thread.Sleep(10000);
//Task task1 = Task.Run(() => Increase());
//Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine(count);
}
//private void Increase()
//{
// mutex.WaitOne();
// for (int i = 0; i < int.MaxValue; i++)
// {
// count++;
// }
// mutex.ReleaseMutex();
//}
private void Decrease()
{
Mutex mutex = new Mutex(true);
for (int i = 0; i < int.MaxValue; i++)
{
count--;
}
mutex.ReleaseMutex();
}
}
2)System.Threading.AbandonedMutexException:“由于出现被放弃的 mutex,等待过程结束。”
msdn中指出,如果一个线程终止时拥有互斥体,则认为该互斥量将放弃。 互斥体的状态将设置为终止状态,并等待下一步的线程获取所有权。也就是说线程程序执行完毕,但是线程用的Mutex对象没有release,如果这时再次申请互斥体,则认为互斥体的代码已经执行完毕了,申请互斥体的线程可以获取互斥体,但是这里有一个线程申请互斥体时间的问题,如果拥有互斥体的线程1中的代码没有执行完毕,线程2调用waitone申请互斥体,那么当线程1代码执行完毕,但是没有release掉mutex的话,则线程2就会报System.Threading.AbandonedMutexException这个错误,所以我们在使用中一定要注意释放Mutex的实例,报错代码如下:
public partial class Form1 : Form
{
private static int count = 0;
Mutex mutex;
public Form1()
{
InitializeComponent();
Task task2 = Task.Run(() => Decrease());
Task task1 = Task.Run(() => Increase());
Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine(count);
}
private void Increase()
{
mutex.WaitOne();
for (int i = 0; i < int.MaxValue; i++)
{
count++;
}
mutex.ReleaseMutex();
Console.WriteLine("递增线程结束");
}
private void Decrease()
{
mutex = new Mutex(true);
for (int i = 0; i < int.MaxValue; i++)
{
count--;
}
//mutex.ReleaseMutex();
Console.WriteLine("递减线程结束");
}
}
如果上述代码中线程task1在task2执行完毕后再去申请互斥体,则不会报错,代码如下:
public partial class Form1 : Form
{
private static int count = 0;
Mutex mutex;
public Form1()
{
InitializeComponent();
Task task2 = Task.Run(() => Decrease());
Thread.Sleep(10000);
Task task1 = Task.Run(() => Increase());
Task.WaitAll(new Task[] { task1, task2 });
Console.WriteLine(count);
}
private void Increase()
{
mutex.WaitOne();
for (int i = 0; i < int.MaxValue; i++)
{
count++;
}
mutex.ReleaseMutex();
Console.WriteLine("递增线程结束");
}
private void Decrease()
{
mutex = new Mutex(true);
for (int i = 0; i < int.MaxValue; i++)
{
count--;
}
//mutex.ReleaseMutex();
Console.WriteLine("递减线程结束");
}
}