线程锁 线程并发处理 | 异步调用委托的使用
来源:互联网 发布:centos tmp目录 编辑:程序博客网 时间:2024/05/14 14:55
《》
lock锁:两个(多个)线程访问一个变量,存在并发性的问题
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace ConsoleApplication1{ class Program { static int max=10000000; static long _count = 0; static void Main(string[] args) { //Console.WriteLine("主线程运行"); //t1线程递减 Thread t1 = new Thread(new ThreadStart(() => { for (int i = 0; i < max; i++) { //_count--; _count = _count - 1; } })); t1.Start(); Console.WriteLine("t1线程已经启动,开始对_count变量++"); Console.WriteLine("主线程继续执行......."); //主线程递增 for (int i = 0; i < max; i++) { _count++; } t1.Join(); Console.WriteLine("_count的值是" + _count); Console.ReadKey(); } }}
程序的执行结果,并没有我们想象中的_count=0
原因如下:
其实_count++ 的意思是:_count=_count+1
总共分为三步: 第一步:把_count变量中的值取出来 第二步:将取出来的值加1 第三部:将加1后的值在赋值给_count
现在是主线程执行_count++的任务,而子线程t1执行的是_count-- 的任务
因为我们不只是CPU什么时候调度主线程,什么时候调度子线程,那我们可以做个假设。假如cpu最先调度主线程把0从_count中取出,然后++变成了1 然后又把1赋值给_count ,而当主线程执行了这三步了,而子线程t1一次都没被调用,单主线程执行第四部在把把1从_count中取出,t1这个子线程还是没有被CPU调度执行。在第五步的时候,此时cpu调度了子线程t1,而没有调度主线程,此时t1这个线程里执行的是 从_count中把1取出 ,而就在t1从_count中把1取出后,CPU又去调度主线程去了
所以第六步 ,主线程执行的是 把取出的1加加结果是2,然后第七步CPU又没有调度子线程,而是调度的是主线程,此时主线程执行的是:把2赋值给_count
当第八步的时候,CPU终于调度子线程t1了。因为在第五步的时候t1已经从_count中把1取出 所以 第八步的时候子线程执行--操作,把取出的1递减结果是0 而第九步CPU有是调度t1这个子线程 ,子线程t1执行的是 把0赋值给_ount
以上结论是 假如主线程和子线程里的循环都循环1千万次,当主线程调用了6次的时候将_count从0自增到了2 而子线程仅仅执行了3次就将“2”--到0,其实这样说不对,应该说
将2直接赋值为0了
解决并发操作的的问题的方法 lock 锁
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace ConsoleApplication1{ class Program { static int max=10000000; static long _count = 0; //锁的使用步骤: //1.建立一个锁的对象,锁对象必须是引用类型。(而且一般是只读的) //如果使用值类型,会在加锁和解锁的时候造成装箱和拆箱的问题,加锁的时候使用一个对象,而解锁的时候使用的是另外一个对象 //一个项目可以建立多个锁 //锁的使用: //假如有3个线程并发访问变量A,那么就为这个3个线程建立一个锁。 假如又有5个线程并发访问变量B,那么就再为这个5个线程再建立一个锁 //使用锁会导致程序变慢(变慢的主要原因就是cpu调度线程的时候进行线程切换需要时间),所以只有在防止并发的时候才加锁 static readonly object objSync = new object();//建立这个objSync对象的意义,就是让lock里面的代码同步,没有其他的任何意义。 static void Main(string[] args) { //Console.WriteLine("主线程运行"); //t1线程递减 Thread t1 = new Thread(new ThreadStart(() => { for (int i = 0; i < max; i++) { //在lock块中的代码同时只能有一个线程来访问(因为主线程和t1线程会并发访问_count这个变量,所以在使用_count的时候都将他们锁起来) lock (objSync) { //_count--; _count = _count - 1; } //下面解释为什么锁对象必须是引用类型 //通过反编译得知 lock语句其实被编译成了下面的代码: /* bool isLockOK = false;//标记当前变量是否已经上锁 Monitor.Enter(objSync,ref isLockOK); //上锁(因为Enter的参数类型都要求的是object类型,如果你的锁定义为值类型,而这里又需要一个引用类型,当上锁的时候,假如此时你传递一个值类型10进来,由于Enter的参数是引用类型,就会发生装箱,装箱的话,堆里就参数一个新的对象了。然后再下面的解锁过程中,你再把值类型10传递进来,又发生一次装箱,堆里又生成一个对象了。这样就导致加锁和解锁不是同一个对象) try { _count--; } finally { if(isLockOK) { Monitor.Exit(objSync); //解锁 (Exit的参数类型都要求的是object类型) } } */ } })); t1.Start(); Console.WriteLine("t1线程已经启动,开始对_count变量++"); Console.WriteLine("主线程继续执行......."); //主线程递增 for (int i = 0; i < max; i++) { //在lock块中的代码同时只能有一个线程来访问 lock (objSync) { _count++; } } t1.Join(); Console.WriteLine("_count的值是" + _count); Console.ReadKey(); } }}上了锁后,发现_count的值最后为0 了
线程池
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace 线程池{ class Program { static void Main(string[] args) { //使用线程池的目的就是它能内部帮我维护,并且写起来简单。不好的地方就是,因为它里面是内部创建的线程,我们不能手动控制比如Sleep,Join等等 //线程池的使用:比较耗时,比较长的任务不要使用线程池,还是自己去创建一个线程,不要用线程池。只有比较短的,比较不耗时的就使用线程池 //ThreadPool线程池里面有一个QueueUserWorkItem方法,在这个方法里,可以把我们要异步执行的方法放到这个QueueUserWorkItem队列里面,放到这个队列里面,在它内部它会自动帮你启动这个线程,帮你执行,并且线程池中创建的所有线程都是后台线程,没有前台线程 ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => { //这里的方法就是要进行异步执行的方法 for (int i = 0; i < 10; i++) { Console.WriteLine(obj);//这里会输出10个"你好" Thread.Sleep(1000); } }), "你好");//第二个参数其实就是第一个参数,即方法使用的数据对象。因为WaitCallback这个委托的第一个参数是一个方法,第二个参数是这个方法的参数 //在创建一个线程池 ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => { //这里的方法就是要进行异步执行的方法 for (int i = 0; i < 10; i++) { Console.WriteLine(obj);//这里会输出10个"你好" Thread.Sleep(1000); } }), "我好"); Console.ReadKey(); } }}
异步委托的使用(异步委托使用的是线程池中的线程,所以它是一个后台线程)
先来看看正常的委托是怎么使用的:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace 异步调用委托{ //声明一个委托 public delegate int Mydelegate(int n1, int n2); class Program { static void Main(string[] args) { //正常的委托是这样使用的 Mydelegate md = new Mydelegate(GetSum); int a=md(1, 100); //这里通过int a就可以获取md这个委托的返回值了 Console.ReadKey(); } static int GetSum(int a1,int a2) { int sum=0; for (int i = a1; i <= a2; i++) { sum += i; Thread.Sleep(10);//每循环一次,就让这个主线程休息0.01秒钟 } Console.WriteLine("方法中输出的结果是:{0}", sum); return sum; } }}
在来看看异步委托如何使用:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace 异步调用委托{ //声明一个委托 public delegate int Mydelegate(int n1, int n2); class Program { static void Main(string[] args) { //正常的委托是这样使用的 //Mydelegate md = new Mydelegate(GetSum); //int a=md(1, 100); //这里通过int a就可以获取md这个委托的返回值了 //Console.WriteLine(a); //在来看看异步委托的是如何使用的: Mydelegate md1 = new Mydelegate(GetSum); //开始异步调用委托 IAsyncResult result = md1.BeginInvoke(1, 100, null, null);//我们发现BeginInvoke这个方法是有放回值的,返回值是IAsyncResultl接口类型的(BeginInvoke()方法的返回值就是返回了一个对象,这个对象实现了IAsyncResult接口,并且该对象中封装了一些关于担起异步执行的委托的一些状态信息) //在调用BeginInvoke方法与调用EndInvoke方法之间我们可以做我们想做的任何事情;例如: for (int i = 0; i < 10; i++) { Console.WriteLine("主线程继续执行......"); } // 注意:EndInvoke方法既然要拿到异步调用的委托的放回值,就需要等待异步委托(方法)执行完毕,所以它就会阻塞线程 // ↓ //获取异步调用的委托(方法)的返回值(EndInvoke是调用结束的意思,这里是调用结束并获取异步调用的方法的返回值) int aa = md1.EndInvoke(result); //我们发现EndInvoke这个方法的参数是一个IAsyncResult类型。 所以这里我们将result传递进来。(注意:EndInvoke方法的返回值类型是根据md1这个委托的返回值类型来确定的,因为md1这个委托的返回值类型是int类型,所以此时EndInvoke的返回值类型是int类型。) Console.WriteLine("异步调用的委托(方法)的返回值为{0}", aa); Console.WriteLine("主线程继续执行......."); Console.ReadKey(); } static int GetSum(int a1, int a2) { int sum = 0; for (int i = a1; i <= a2; i++) { sum += i; Thread.Sleep(10);//每循环一次,就让这个主线程休息0.01秒钟 } Console.WriteLine("方法中输出的结果是:{0}", sum); return sum; } }}
我们希望开启这个异步调用委托后,异步调用的委托该干什么就干什么,而主线程也是该干什么就干什么(不想被阻塞)。等子线程执行完毕这个异步调用的委托后,告诉我执行的结果(返回值)就行了
可是因为当我调用EndInvoke的时候会阻塞主线程,虽然我们可以在调用EndInvoke之前做一些我想要做的事,可是只要调用EndInvoke,只要异步委托没有执行完毕就会阻塞主线程,那么我现在就不想让它阻塞线程。当BeginInvoke开始异步调用委托后,等这个委托执行完毕后返回值告诉我,我这里一下都不想等你(即:不想等异步调用的委托执行完毕)怎么办呢?
这时候我们可以使用BeginInvoke方法的第三个参数:AsyncCallback委托,这个委托执行是回调方法;这个回调方法的作用是:等异步调用的委托执行完毕后执行此方法
可是因为当我调用EndInvoke的时候会阻塞主线程,虽然我们可以在调用EndInvoke之前做一些我想要做的事,可是只要调用EndInvoke,只要异步委托没有执行完毕就会阻塞主线程,那么我现在就不想让它阻塞线程。当BeginInvoke开始异步调用委托后,等这个委托执行完毕后返回值告诉我,我这里一下都不想等你(即:不想等异步调用的委托执行完毕)怎么办呢?
这时候我们可以使用BeginInvoke方法的第三个参数:AsyncCallback委托,这个委托执行是回调方法;这个回调方法的作用是:等异步调用的委托执行完毕后执行此方法
using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Remoting.Messaging;using System.Text;using System.Threading;using System.Threading.Tasks;namespace 异步调用委托{ //声明一个委托 public delegate int Mydelegate(int n1, int n2); class Program { static void Main(string[] args) { Console.WriteLine("主线程的线程Id:{0}", Thread.CurrentThread.ManagedThreadId); //正常的委托是这样使用的 //Mydelegate md = new Mydelegate(GetSum); //int a=md(1, 100); //这里通过int a就可以获取md这个委托的返回值了 //Console.WriteLine(a); //在来看看异步委托的是如何使用的: Mydelegate md1 = new Mydelegate(GetSum); //开始异步调用委托 IAsyncResult result = md1.BeginInvoke(1, 100, new AsyncCallback(Callback),"第四个参数是第三个参数的参数,注:第三个参数是一个委托");//我们发现BeginInvoke这个方法是有放回值的,返回值是IAsyncResultl接口类型的 Console.WriteLine("主线程继续执行完毕"); Console.ReadKey(); } //自定义一个回调函数,这个回调函数由系统自动调用,当指定的异步委托调用完毕后,自动调用该方法 static void Callback(IAsyncResult syncResult) { Console.WriteLine("回调函数中的线程Id:{0}", Thread.CurrentThread.ManagedThreadId); Console.WriteLine("回调函数的参数:{0}", syncResult.AsyncState);//因为我这个测试无需用到第三个参数的参数,所以第四个参数无意义,这里就打印出来看看而已 //在回调函数中调用EndInvoke()方法就可以获取返回值 //需要把接口类型转换成具体的对象 AsyncResult ar = syncResult as AsyncResult; //注意:在这个回调函数中调用EndInvoke方法不会阻塞线程,因为异步委托调用完毕后才执行这个方法 //AsyncDelegate是获取在其上调用异步调用的委托对象。这里先是将ar.AsyncDelegate转换成Mydelegate类型 int sum = ((Mydelegate)ar.AsyncDelegate).EndInvoke(ar); Console.WriteLine("返回值是:{0}", sum); } static int GetSum(int a1, int a2) { Console.WriteLine("执行GetSum()方法的线程Id:{0}", Thread.CurrentThread.ManagedThreadId); int sum = 0; for (int i = a1; i <= a2; i++) { sum += i; Thread.Sleep(10);//每循环一次,就让这个主线程休息0.01秒钟 } Console.WriteLine("方法中输出的结果是:{0}", sum); return sum; } }}
打印结果得知:
执行GetSum()方法的线程Id与回调函数中的线程Id是一样的,即执行它们的是同一个线程(非主线程)
0 0
- 线程锁 线程并发处理 | 异步调用委托的使用
- 使用委托的异步调用实现线程
- 线程、委托的异步调用
- (二)线程--通过委托异步调用方法
- 线程的异步调用
- vb.net的异步读写数据流(使用线程、委托)
- vb.net的异步读写数据流(使用线程、委托)
- (并发)线程的处理
- 线程、线程池、并发、同步、异步、锁
- 线程 和异步委托
- 线程之异步委托
- C#构建多线程应用程序(1) —— 使用委托来自动创建次线程以实现异步方法的调用
- (二)线程--通过委托异步调用方法(示例下载)
- (二)线程--通过委托异步调用方法(示例下载)
- (二)线程--通过委托异步调用方法(示例下载)
- CLR via C#线程池与委托异步调用
- 谈.Net委托与线程——创建无阻塞的异步调用
- 谈.Net委托与线程——创建无阻塞的异步调用(一)
- C++基础---浅层及深层拷贝构造函数
- 九度 题目1020:最小长方形
- 类似百度搜索后分页的实现
- CSS3选择器详解
- redis源码阅读[0]
- 线程锁 线程并发处理 | 异步调用委托的使用
- How many ways??(离散数学知识(集合论二元关系Warshall算法原理)+矩阵快速幂)
- 51单片机串口程序
- 手机淘宝列表页面 的js调用展示
- jQuery.ajax()方法中参数详细解析
- iOS开发分分钟搞定C语言 —— 总结
- Apriori算法详解
- Java日志框架——SLF4J+Logback配置
- 20150804 Ajax基础