当前位置:编程学习 > C#/ASP.NET >>

asp.net 多线程编程详解(1/2)

在线程间共享数据有可能会导致竞速状态而发生数据不一致的状态, 例如:

 代码如下 复制代码
namespace TaskParallel
{
    class Account
    {
        public int Balance
        {
            get;
            set;
        }
    }
    class Share
    {
        static void Main(string[] args)
        {
            Account account = new Account { Balance = 0 };
            List<Task> tasks = new List<Task>();
            for (int i = 0; i < 10; i++)
            {
                tasks.Add(Task.Factory.StartNew(() =>
                 {
                     for (int j = 0; j < 1000; j++)
                         account.Balance++;    
                 }));
            }
            Task.WaitAll(tasks.ToArray());
            Console.WriteLine(account.Balance);           
        }
    }
}

这段程序中,一共有10个线程,每个线程将一个整型变量自加1000次,期待的结果最终应该是10000,但是运行这段程序的结果每次都不一样而且总比10000小。原因是完成一个变量自加并不是一个原子操作,忽略具体的机器代码不谈,总体上应该是三步,读取当前值,加1,存回计算值.如果线程1读取到了当前值是0,此时被线程2取代而进入等待状态,线程二读取当前值为0,加1,把1存回,线程1接着运行,加1,把1存回。此时Balance的值是1,而已经有线程1和线程2加了2次,数据不一致发生了。下面介绍.Net提供的线程互斥的方法,其实现原理在操作系统原理类的书上有详细介绍,不再赘述。

 

1. 使用Monitor

为了避免不一致发生,必须保证能够改变共享数据的代码在同一时间只有一个线程在执行,要实现这一点,可以使用C#的lock关键字:

 代码如下 复制代码
object obj = new object();for (int i = 0; i < 10; i++)
{
     tasks.Add(Task.Factory.StartNew(() =>
     {
           for (int j = 0; j < 1000; j++)
           {
                 lock (obj)
                 {
                      account.Balance++;
                 }
            }
        }));
}

lock其实是Monitor类的一个包装,要使用更为完整的功能可以使用Monitor类.2.使用Spin Locking

Spin Locking和Monitor实现的效果相似,但是原理不一样。Spin Locking不阻塞当前线程,而是用一个循环来不断判断是否符合访问条件。当预期阻塞的时间不太长的时候,他比Monitor类高效,但是不适合需要长时间阻塞的情况.

 代码如下 复制代码
SpinLock locker = new SpinLock();           
for (int i = 0; i < 10; i++)
{     tasks.Add(Task.Factory.StartNew(() =>
     {
          for (int j = 0; j < 1000; j++)
          {
               bool lockAcquired = false;
               try
               {
                   locker.Enter(ref lockAcquired);
                   account.Balance++;
               }
               finally
               {
                    locker.Exit();
               }                      
           }
       }));
}

3.使用Mutex,Semaphore

Mutex,Semaphore都继承自WaitHandle类,可以实现线程互斥的。WaitHandle是windows的synchronization handles的包装。

先介绍Mutex的例子:

 代码如下 复制代码
Mutex mutex = new Mutex();
for (int i = 0; i < 10; i++)
{
     tasks.Add(Task.Factory.StartNew(() =>
     {
         for (int j = 0; j < 1000; j++)
         {
              bool lockAcquired = mutex.WaitOne();
              account.Balance++;                     if(lockAcquired)
                 mutex.ReleaseMutex(); 
          }
     }));
}

WaitHandle的WaitAll方法可以同时获得多个锁,例如在下面的程序中,有两个账户,需要两个锁来保持他们在同一时间只有一个线程访问。其中第三个线程同时访问这两个账户,因此需要同时获得这个两个账户的锁,当第三个线程结束访问的时候,要记得释放两个锁。

 

 代码如下 复制代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace TaskParallel
{
    class Account
    {
        public int Balance
        {
            get;
            set;
        }
    }
    class Share
    {
        static void Main(string[] args)
        {
            Account account1 = new Account { Balance = 0 };
            Account account2 = new Account { Balance = 0 };
            Mutex mutex1=new Mutex();
            Mutex mutex2=new Mutex();

            Task t1 = new Task(() =>
            {
                for (int i = 0; i < 1000; i++)
                {
                    bool locked = mutex1.WaitOne();
                    account1.Balance++;
                    if (locked)
                        mutex1.ReleaseMutex();
                }
            });

            Task t2 = new Task(() =>
            {
                for (int i = 0; i < 1000; i++)
                {
                    bool locked = mutex2.WaitOne();
                    account2.Balance++;
                    if (locked)
                        mutex2.ReleaseMutex();
                }
            });

            Task t3 = new Task(() =>
            {
                for (int i = 0; i < 1000; i++)
                {
                    bool locked=WaitHandle.WaitAll(new WaitHandle[] { mutex1, mutex2 });
                    account1.Balance--;
                    account2.Balance--;
                    if (locked)   //release two locks
                    {
                        mutex1.ReleaseMutex();
                        mutex2.ReleaseMutex();
                    }
                }
            });

            t1.Start();
            t2.Start();
            t3.Start();
            Task.WaitAll(t1, t2, t3);
            Console.Write("Balance1:{0}, Balance2:{1}", account1.Balance, account2.Balance);
            Console.ReadLine();
        }
    }
}

1 2
补充:asp.net教程,.Net开发
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,