c#之线程同步浅析(1)-----轻量级同步Interlocked
来源:互联网 发布:qq飞车帝王数据 编辑:程序博客网 时间:2024/05/17 08:30
转载自:http://blog.csdn.net/kkfdsa132/article/details/5474013
在c#里面,实现线程同步方法有很多种。我了解到的至少有8种。这里先讲下,一种比较简单的同步方法-------轻量级同步Interlock。
为什么说它是轻量级呢?因为它仅对整形数据(即int类型,long也行)进行同步。如果你学过操作系统里面的PV操作(即信号量),那么你对它已经了解了一般。它实现的正是如同信号量的功能。下面是它提供的方法:
Interlocked.Increment(ref value)数值加一(原子性操作)Interlocked.Decrement(ref value)数值减一(原子性操作)Interlocked.Exchange(ref value1, value2)交换:把值2赋给值1;返回新值Interlocked.CompareExchange(ref value1, value2, value3)实现比较和交换两种功能:值1和值3比较,如果相同,把值2给值1,不相同则不作任何操作;返回原值(多用于判断条件)(示例3中会用到)
下面是它的几个用法示例:
(1)示例1:简单输出
- int num = 0;
- Interlocked.Increment(ref num);
- Console.WriteLine(num);
- Interlocked.Decrement(ref num);
- Console.WriteLine(num);
- Interlocked.Exchange(ref num, 10);
- Console.WriteLine(num);
- Console.WriteLine(Interlocked.CompareExchange(ref num, 100, 10));
- Console.WriteLine(num);
输出结果如下:
(2)示例2:创建1个后台进程和50个前台进程。后台用于每隔一秒通报当期有几个进程在运行。50个前台进程则什么事都不敢,只随机停留1~12秒。代码如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading;
- namespace InterlockExample
- {
- class Program
- {
- //记录线程数
- private static int threadCount = 0;
- private static Random rnd = new Random();
- /// <summary>
- /// 创建一个线程,什么事也不干, 只随机停留1~12秒
- /// </summary>
- private static void RndThreadFunc()
- {
- //新建线程,线程数加一
- Interlocked.Increment(ref threadCount);
- try
- {
- //什么事也不干, 只随机停留1~12秒
- int sleepTime = rnd.Next(1000, 12000);
- Thread.Sleep(sleepTime);
- }
- finally
- {
- //线程结束,线程数减一
- Interlocked.Decrement(ref threadCount);
- }
- }
- /// <summary>
- /// 每隔一秒,通报有几个线程存在
- /// </summary>
- private static void RptThread()
- {
- while(true)
- {
- int threadNumber = 0;
- //获取当前线程数
- threadNumber = Interlocked.Exchange(ref threadCount, threadCount);
- Console.WriteLine("{0} Thread(s) alive ", threadNumber);
- //休眠1秒
- Thread.Sleep(1000);
- }
- }
- static void Main(string[] args)
- {
- //创建RptThread线程,每隔一秒通报有几个线程存在
- Thread reporter = new Thread(new ThreadStart(RptThread));
- //设置为后台线程
- reporter.IsBackground = true;
- reporter.Start();
- //创建50个RndThreadFunc线程
- Thread[] rndThreads = new Thread[50];
- for (int i = 0; i < 50; i++)
- {
- rndThreads[i] = new Thread(new ThreadStart(RndThreadFunc));
- rndThreads[i].Start();
- }
- }
- }
- }
结果如下:(答案不唯一,因为线程是随机停留的)
(3)示例3:对写文件的加锁操作。我们知道读一个文件可以允许几个人同时进行,而写操作则每次只允许一人。这里创建5个写的进程,每次只能有一个进程可以写,其他进程必须等待当前写进程退出,才可进入。代码如下:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using System.Threading; //线程
- using System.IO; //文件流
- namespace InterlockExample
- {
- /// <summary>
- /// 一个类似于自旋锁的类,也类似于对共享资源的访问机制
- /// 如果资源已被占有,则等待一段时间再尝试访问,如此循环,直到能够获得资源的使用权为止
- /// </summary>
- public class SpinLock
- {
- //资源状态锁,0--未被占有, 1--已被占有
- private int theLock = 0;
- //等待时间
- private int spinWait;
- public SpinLock(int spinWait)
- {
- this.spinWait = spinWait;
- }
- /// <summary>
- /// 访问
- /// </summary>
- public void Enter()
- {
- //如果已被占有,则继续等待
- while (Interlocked.CompareExchange(ref theLock, 1, 0) == 1)
- {
- Thread.Sleep(spinWait);
- }
- }
- /// <summary>
- /// 退出
- /// </summary>
- public void Exit()
- {
- //重置资源锁
- Interlocked.Exchange(ref theLock, 0);
- }
- }
- /// <summary>
- /// 自旋锁的管理类
- /// </summary>
- public class SpinLockManager : IDisposable //Disposable接口,实现一种非委托资源回收机制,可看作显示回收资源。任务执行完毕后,会自动调用Dispose()里面的方法。
- {
- private SpinLock spinLock;
- public SpinLockManager(SpinLock spinLock)
- {
- this.spinLock = spinLock;
- spinLock.Enter();
- }
- //任务结束后,执行Dispose()里面的方法
- public void Dispose()
- {
- spinLock.Exit();
- }
- }
- /// <summary>
- /// 主类
- /// </summary>
- class Program
- {
- private static Random rnd = new Random();
- //创建资源锁,管理资源的访问
- private static SpinLock logLock = new SpinLock(10);
- //以写的方式打开文件,选择追加模式
- private static StreamWriter fsLog =
- new StreamWriter(File.Open("Log.txt",
- FileMode.Append,
- FileAccess.Write,
- FileShare.None));
- /// <summary>
- /// 写入文件
- /// </summary>
- private static void RndThreadFunc()
- {
- //创建SpinLockManager,并调用Dispose()方法。这里采用using字段,是调用Dispose()方法的形式。
- using (new SpinLockManager(logLock))
- {
- //写入文件
- fsLog.WriteLine("Thread Starting");
- fsLog.Flush();
- }
- int time = rnd.Next(10, 200);
- Thread.Sleep(time);
- //using语句,当在某个代码段中使用了类的实例,只要离开了这个代码段就自动调用这个类实例的Dispose。using只能用于实现了IDisposable接口的类型
- using (new SpinLockManager(logLock))
- {
- fsLog.WriteLine("Thread Exiting");
- fsLog.Flush();
- }
- }
- static void Main()
- {
- Thread[] rndThreads = new Thread[5];
- //创建5个RndThreadFunc的线程
- for (int i = 0; i < 5; i++)
- {
- rndThreads[i] = new Thread(new ThreadStart(RndThreadFunc));
- rndThreads[i].Start();
- }
- }
- }
- }
结果如下:
(4)示例4:发牌程序。游戏简介:有一个发送线程负责发牌,4个接受线程(可以看做4个玩家)轮流接收牌。这里实现的原理很像信号量的机制。具体说明:
1、设有一个发送线程sender,依次产生1-52的数,就好像依次发出52张牌。
2、 同时有4个接收线程Receiver在依次接收Sender发出的52张牌
3、 每一轮中,发送线程Sender发送4张牌,4个接收线程轮流接收这四张牌。
4、 设置发送线程的优先级最高为1,保证接收线程每执行一次,即接收一张牌后,再由发送线程执行一次。
代码如下(在后面的同步线程浅析中,会分别用到其他同步方法(lock,volatile,Monitor等 )实现这个范例,以此作为讲解。):附录该实例下载地址:http://download.csdn.net/source/2230680
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Data;
- using System.Drawing;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- using System.Threading;
- namespace SendCard
- {
- public partial class SendCard : Form
- {
- private CardManager cardManager;
- public SendCard()
- {
- InitializeComponent();
- //该句是允许主线外其他线程操控Winform控件,因为在Winform2.0以后,微软加强了控件访问的安全性,是不允许主线程外的其他线程访问控件的。
- //如果不用这句会报错。当然,你也可以采用别的方法屏蔽掉这个错误,例如委托。
- Control.CheckForIllegalCrossThreadCalls = false;
- cardManager = new CardManager(10);
- }
- /// <summary>
- /// 发牌
- /// </summary>
- /// <param name="sender"></param>
- /// <param name="e"></param>
- private void btnSendCard_Click(object sender, EventArgs e)
- {
- //文本框清零
- foreach (Control cnt in this.Controls)
- {
- if (cnt is RichTextBox)
- {
- ((RichTextBox)cnt).Clear();
- }
- }
- //创建发牌和接收牌类的实例
- Sender sender1 = new Sender(52, cardManager); //发送52张牌
- Receiver[] receiver = new Receiver[4];
- //创建线程:1个发牌线程和4个接收牌的线程
- Thread[] receiveThread = new Thread[4];
- Thread sendThread = new Thread(new ThreadStart(sender1.SendCard));
- sendThread.Start();
- //4个接收牌线程
- for (int i = 0; i < 4; i++)
- {
- //依次获取编辑框控件rtbPlayer0~rtbPlayer3
- Control[] rtb = this.Controls.Find("rtbPlayer" + i.ToString(), true);
- receiver[i] = new Receiver(i, cardManager, (RichTextBox)rtb[0], 13);
- receiveThread[i] = new Thread(new ThreadStart(receiver[i].ReceiveCard));
- receiveThread[i].Start();
- }
- }
- }
- /// <summary>
- /// 管理发牌和接受牌的类,类似于信号量的操作。主要有两个信号:1.发牌信号--标记当期是否已发牌 2.接收牌玩家信号---标记轮到哪个玩家接受牌
- /// </summary>
- public class CardManager
- {
- //标记是否已发牌,0--下一张未发,1--已发
- private int hasCard = 0;
- //当前接收牌的玩家号:0~3
- private int order;
- //当前牌号
- private int value;
- //等待时间
- private int waitTime;
- public CardManager(int time)
- {
- order = 0;
- this.waitTime = time;
- }
- /// <summary>
- /// 发牌
- /// </summary>
- /// <param name="value">牌号</param>
- public void PutFunc(int value)
- {
- //已发牌则继续等待,牌给取走,下一次发牌
- while (Interlocked.Exchange(ref hasCard, hasCard) == 1)
- {
- Thread.Sleep(waitTime);
- }
- //新牌号
- this.value = value;
- //重置发牌状态:已发
- Interlocked.Exchange(ref hasCard, 1);
- }
- public int GetFunc(int order)
- {
- //等待接收牌,如果牌仍未发放或不是发给该玩家,则继续等待
- while (Interlocked.Exchange(ref hasCard, hasCard) == 0 || Interlocked.Exchange(ref this.order, this.order) != order)
- {
- Thread.Sleep(waitTime);
- }
- //更改为下一个接收牌的玩家序号
- Interlocked.Exchange(ref this.order, (order+1)%4);
- //重置发牌状态:未发
- Interlocked.Exchange(ref hasCard, 0);
- return value;
- }
- }
- /// <summary>
- /// 接收牌类
- /// </summary>
- public class Receiver
- {
- //玩家序号
- private int order;
- //牌管理对象
- private CardManager carManager;
- //文本编辑框
- private RichTextBox rtbPlay;
- //牌数量
- private int cardSum;
- /// <summary>
- /// 接收牌--构造函数
- /// </summary>
- /// <param name="order">玩家序号</param>
- /// <param name="carManager">牌管理对象</param>
- /// <param name="rtbPlay">文本编辑框</param>
- /// <param name="cardSum">要接受的牌数量</param>
- public Receiver(int order, CardManager carManager, RichTextBox rtbPlay, int cardSum)
- {
- this.order = order;
- this.carManager = carManager;
- this.rtbPlay = rtbPlay;
- this.cardSum = cardSum;
- }
- /// <summary>
- /// 接收牌
- /// </summary>
- public void ReceiveCard()
- {
- for (int i = 0; i < cardSum; i++)
- {
- //添加到文本框
- rtbPlay.AppendText(carManager.GetFunc(order) + " ");
- Thread.Sleep(100);
- }
- }
- }
- /// <summary>
- /// 发牌类
- /// </summary>
- public class Sender
- {
- //牌数量
- private int cardSum;
- //牌管理对象
- private CardManager cardManager;
- public Sender(int cardSum, CardManager cardManager)
- {
- this.cardSum = cardSum;
- this.cardManager = cardManager;
- }
- /// <summary>
- /// 发牌
- /// </summary>
- public void SendCard()
- {
- //标记该牌是否已发
- int[] card = new int[cardSum];
- Random rnd = new Random();
- //全部初始化为未发
- for (int i = 0; i < cardSum; i++)
- {
- card[i] = 0;
- }
- //发牌
- for (int i = 0; i < cardSum; i++)
- {
- int k;
- //随机产生牌号,如果已发,则循环
- do
- {
- k = rnd.Next(cardSum);
- }while(card[k] == 1);
- //发牌
- cardManager.PutFunc(k + 1);
- //标记该牌已发过
- card[k] = 1;
- }
- }
- }
- }
结果如下:
最后,要提醒一下的是:
1.在。net2.0以后,微软加强了控件的安全机制,不允许非主线程的线程非委托操控控件,如上面得例子代码提到的。
当然,有几种方法解决这个问题:
a.在构造函数里添加一条语句:Control.CheckForIllegalCrossThreadCalls = false;
b.采用委托机制,示例如下:
- //1.声明委托
- private delegate void DlgShowThread();
- /// <summary>
- /// 2.定义被调用方法
- /// </summary>
- public void ShowWord()
- {
- textBox1.Text = "Welcom to c#";
- }
- /// <summary>
- ///3. 线程直接调用的方法
- /// </summary>
- public void ShowThread()
- {
- //执行指定委托:Invoke()方法
- this.Invoke(new DlgShowThread(ShowWord));
- }
- private void button1_Click(object sender, EventArgs e)
- {
- //创建一个线程并开始
- Thread threadShow = new Thread(new ThreadStart(ShowThread));
- threadShow.Start();
- }
2.关于控件的访问:
a.遍历同种类型的控件,如上面代码所示:
- foreach (Control cnt in this.Controls)
- {
- if (cnt is RichTextBox)
- {
- ((RichTextBox)cnt).Clear();
- }
- }
如果你的访问的控件,仅是在一个名为panel1的容器里面,你可以修改如下:
- foreach (Control cnt in this.panel1.Controls)
- {
- if (cnt is RichTextBox)
- {
- ((RichTextBox)cnt).Clear();
- }
- }
b.通过字符串名转为control类型再具体转为该控件类型来访问控件。常用于批处理多个名字前缀相同的控件,例如文本框textbox1,textbox2.。。,方法:this.Controls.Find(string controlName, bool boolValue)。这里列出部分代码:
- Control[] rtb = this.Controls.Find("rtbPlayer" + i.ToString(), true);
- receiver[i] = new Receiver(i, cardManager, (RichTextBox)rtb[0], 13);
3.在示例3中出现的IDisposable接口,该接口主要用途是实现非托管资源的回收。在对象生命结束时,会自动调用Dispose里面的方法。简单的说,就是显示回收资源。定义一个IDisposable类和调用它方法,请看下面示例:
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- namespace IDisposableExample
- {
- class Program
- {
- /// <summary>
- /// 定义一个继承IDisposable接口的类
- /// </summary>
- public class MyIDisposable : IDisposable
- {
- public MyIDisposable()
- {}
- //输出一串字符
- public void ShowMessage(string strText)
- {
- Console.WriteLine(strText);
- }
- //Dispose方法,在对象不再使用时,调用的方法。
- public void Dispose()
- {
- ShowMessage("不再使用该对象,销毁对象,回收资源。");
- }
- }
- static void Main(string[] args)
- {
- //调用MyIDisposable类的两种方法,两种方法是等效的
- //方法一:采用using字段
- using (MyIDisposable myIDisposable1 = new MyIDisposable())
- {
- myIDisposable1.ShowMessage("该对象正在工作中。。。。");
- }
- Console.WriteLine();
- //方法二:先创建该对象,再把该对象转为IDisposable类型,调用dispose()方法
- MyIDisposable myIDisposable2 = new MyIDisposable();
- try
- {
- myIDisposable2.ShowMessage("该对象正在工作中。。。。");
- }
- finally
- {
- IDisposable idisposable = myIDisposable2 as IDisposable;
- if (idisposable != null)
- {
- idisposable.Dispose();
- }
- }
- }
- }
- }
希望对大家有用!在后面,我会继续讲解其他同步线程方法。
- c#之线程同步浅析(1)-----轻量级同步Interlocked
- c#之线程同步浅析(1)-----轻量级同步Interlocked
- C#线程同步(1)-------轻量级同步Interlock
- Windows线程同步之互锁函数(Interlocked)
- Windows线程同步之互锁函数(Interlocked)
- .NET线程同步之Interlocked构造
- 线程同步(使用Interlocked类)
- C# InterLocked类 同步 i++
- .NET线程同步之Interlocked和ReadWrite锁
- c#之同步线程浅析2-----Lock与Monitor
- C# 线程同步与线程池 浅析
- 轻量级的线程同步
- 线程同步一:InterLocked系列函数
- Windows线程同步--互锁变量访问(Interlocked)
- Interlocked系列函数线程同步的缺陷
- 线程同步 旋转锁 Interlocked 用户模式同步对象 InterlockedExchange
- Silverlight 2.0使用Lock, Interlocked, EventWaitHandle, Monitor来实现线程同步//C#线程同步的几种方法
- C# 线程同步之Monitor
- web十大框架:
- POJ 1678 I Love this Game! 笔记
- spring的AOP(二)----JDK动态代理
- Cmake使用示例与整理
- 初读《黑客与画家》
- c#之线程同步浅析(1)-----轻量级同步Interlocked
- 关于crul无法链接https的问题
- Kotlin基础教程-包
- Java file 文件的相关使用
- 【Shader实战篇】PBR贴图之Metallic贴图
- 拉普拉斯分布
- 多条目
- Linux 设备驱动 ====> 并发控制 --- 信号量与互斥体
- 【bzoj4373】算术天才⑨与等差数列