线程锁 线程并发处理 | 异步调用委托的使用

来源:互联网 发布: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委托,这个委托执行是回调方法;这个回调方法的作用是:等异步调用的委托执行完毕后执行此方法
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
原创粉丝点击