【Basic Code】浅谈线程锁及Queue队列

       项目中,下载游戏这块有一个需求,每一时刻正在下载的游戏个数不能超过5个,如果超过5个,则置为等待状态,这5个中的一个下载并安装完毕后,等待状态中的一个游戏开始下载。

       由于这个项目是我中途入手,下载这块的代码是公司某大神写的,看了之后比较复杂,基本上“Task任务 + 多线程 + Lock锁 + 轮询机制”完成这个工作(WCF + UDP协议提供两个端之间的通信、广播支持),既然用到了这些个知识点,就先逐个击破,对于与Task对应的lock锁做如下浅显理解:

     一、Lock是个啥?

     lock 关键字将语句块标记为临界区,方法是获取给定对象的互斥锁,执行语句,然后释放该锁。(类似于我们在数据结构中学习过的PV操作和临界区,参考银行家算法)。

     lock使用时的几点注意:

  lock 关键字在块的开始处调用 Enter,而在块的结尾处调用 Exit。 ThreadInterruptedException 引发,如果 Interrupt 中断等待输入 lock 语句的线程。

     通常,应避免锁定 public 类型,否则实例将超出代码的控制范围。 常见的结构 lock (this)lock (typeof (MyType)) 和 lock ("myLock") 违反此准则:    

     (1) 如果实例可以被公共访问,将出现 lock (this) 问题。

     (2)如果 MyType 可以被公共访问,将出现 lock (typeof (MyType)) 问题。

     (3)由于进程中使用同一字符串的任何其他代码都将共享同一个锁,所以出现 lock("myLock") 问题。

      最佳做法是定义 private 对象来锁定, 或 private static 对象变量来保护所有实例所共有的数据,并且在lock正文中,不能使用thread.wait等等待关键字。


     二、一个lock的demo

#region 使用了Lock锁
class Account
{
	private Object thisLock = new Object();
	int balance;
	Random r = new Random();
	public Account(int initial)
	{
		balance = initial;
	}
	int Withdraw(int amount)
	{
		if (balance < 0)
		{
			throw new Exception("Negative Balance");
		}

		lock (thisLock)
		{
			if (balance >= amount)
			{
				Console.WriteLine("Balance before Withdrawal :  " + balance);
				Console.WriteLine("Amount to Withdraw        : -" + amount);
				balance = balance - amount;
				Console.WriteLine("Balance after Withdrawal  :  " + balance);
				return amount;
			}
			else
			{
				return 0; 
			}
		}
	}
	public void DoTransactions()
	{
		for (int i = 0; i < 100; i++)
		{
			Withdraw(r.Next(1, 100));
		}
	}
}
class Test
{
	static void Main()
	{
		Thread[] threads = new Thread[10];
		Account acc = new Account(1000);
		for (int i = 0; i < 10; i++)
		{
			Thread t = new Thread(new ThreadStart(acc.DoTransactions));
			threads[i] = t;
		}
		for (int i = 0; i < 10; i++)
		{
			threads[i].Start();
		}
		Console.ReadKey();
	}
}
#endregion

      通过Task任务,启动10个线程,来调用Account的Withdraw方法, 一般情况下,10个线程几乎同时调用这个方法的时候,算钱这块,before draw , draw money,after draw就会出错,必须保证是一个线程完整的来调用这个方法之后,另一个线程再来调用,这样子Account的余额才不会算错,于是乎在这里加上一个lock锁

lock (thisLock)
{
	if (balance >= amount)
	{
		Console.WriteLine("Balance before Withdrawal :  " + balance);
		Console.WriteLine("Amount to Withdraw        : -" + amount);
		balance = balance - amount;
		Console.WriteLine("Balance after Withdrawal  :  " + balance);
		return amount;
	}
	else
	{
		return 0; // transaction rejected
	}
}
     代码执行效果如图:

             

        可以算一下,Account没有差错。

        对于没有使用lock的情况:

        

       可以看出,对于一套数据操作的顺序都是错的,更别说数据准确性了。

       可以说lock这个操作,就像是高速路的收费站,如图:

      

      各个线程就像是拥堵在告诉路出口的汽车,保证原子性、完整性的前提下,汽车一辆辆的从出口出来。这样子虽然会造成暂时的阻塞,但是对于数据的准确性则得到了保证,对于类似于秒杀、争夺有限的数据库资源的地方, 就可以使用lock方法来完成。

      既然说到了这里,就不得不提一下Queue这个东东,队列也是在数据结构里学习过的,在.Net体系当中被称为Queue,最大的特征就是先进先出,码一段代码:

static void Main(string[] args)
{
	Queue q = new Queue();
	q.Enqueue('A');
	q.Enqueue('B');
	q.Enqueue('C');
	q.Enqueue('D');
	Console.WriteLine("当前队列: ");
	foreach (var c in q)
		Console.Write(c + " ");
	Console.WriteLine();
	q.Enqueue('E');
	q.Enqueue('F');
	Console.WriteLine("当前队列: ");
	foreach (var c in q)
		Console.Write(c + " ");
	Console.WriteLine();
	Console.WriteLine("移除队列中的几个值 ");
	char ch = (char)q.Dequeue();
	Console.WriteLine("移除的值为: {0}", ch);
	ch = (char)q.Dequeue();
	Console.WriteLine("移除的值为: {0}", ch);
	Console.WriteLine();
	foreach (char c in q)
		Console.Write(c + " ");
	Console.ReadKey();
}
     最为常用的两个方法, Queue.Enqueue()以及Queue.Dequeue(),分别是压入队列和弹出队列,如上,压入A、B、C、D、E、F之后,弹出了队列中的两个值,运行结果如图:

     

     在项目中的应用,将日志写入到数据库日志表中时,为保证每条日志的先后顺序,操作核心代码为:

{
	while (true)
	{
		MyLog log = null;
		lock (SyncObj)
		{
			if (_logQueue.Count > 0)
				log = _logQueue.Dequeue();
		}
		if (log != null)
		{
			try
			{
				Db_Log dbLog = new Db_Log()
				{
					Caption = log.Caption,
					Msg = log.Msg,
					Type = (int)log.Type,
					Level = (int)log.Level,
					Dt = ConvertDateTimeToInt(log.Dt)
				};
				DbSession.Insert(dbLog);
			}
			catch (Exception ex)
			{
			}
		}
		if (_logQueue.Count > 0)
			continue;
		autoEvent.WaitOne();
	}
}
     这里主要是通过 _ logQ ueue的count值,不断循环进行弹出操作,直至_logQueue的count值为0.

       对于Queue,如图:

     至此,初步的lock以及queue完成,下一篇分析其在项目中下载游戏处的实际应用。



  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 22
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值