大学IT网 - 最懂大学生的IT学习网站! QQ资料交流群:367606806
当前位置:大学IT网 > C#技巧 > C#并行编程-线程同步原语

C#并行编程-线程同步原语(4)

关键词:并行编程线程同步C#  阅读(2327) 赞(19)

[摘要]本文是对C#并行编程-线程同步原语的讲解,对学习C#编程技术有所帮助,与大家分享。

    class Program
    {
        private static Task[] _CookTasks { get; set; }
        private static object o = new object();
        private static StringBuilder AppendStrUnLock = new StringBuilder();
        private static StringBuilder AppendStrLock = new StringBuilder();
        private static StringBuilder AppendStrMonitorLock = new StringBuilder();
        /*获取当前计算机处理器数*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:释迦苦僧   */
        static void Main(string[] args)
        {
            SpinLock sl = new SpinLock();
            _CookTasks = new Task[_particpants];
            Thread.Sleep(4000);
            Stopwatch swTask1 = new Stopwatch();
            swTask1.Start();
            for (int task_index = 0; task_index < _particpants; task_index++)
            {
                _CookTasks[task_index] = Task.Factory.StartNew((num) =>
                {
                    Parallel.For(1, 200000, (i) =>
                    {
                        string str = "append message " + i;
                        bool lockTaken = false;
                        try
                        {
                            sl.Enter(ref lockTaken);
                            AppendStrMonitorLock.Append(str);
                        }
                        finally
                        {
                            if (lockTaken)
                                sl.Exit();
                        }
                    });
                }, task_index);
            }

            /*ContinueWhenAll 提供一组任务完成后 延续方法*/
            var finalTask = Task.Factory.ContinueWhenAll(_CookTasks, (tasks) =>
            {
                /*等待任务完成*/
                Task.WaitAll(_CookTasks);
                swTask1.Stop();
                Console.WriteLine("采用SpinLock操作,字符串长度:{0},耗时:{1}", AppendStrMonitorLock.Length, swTask1.ElapsedMilliseconds);
                /*释放资源*/
            });

            Console.ReadLine();
        }
    }

在实际的编程中需要注意的是:不要将SpinLock声明为只读字段,如果声明为只读字段,会导致每次调用都会返回一个SpinLock新副本,在多线程下,每个方法都会成功获得锁,而受到保护的临界区不会按照预期进行串行化。

基于自旋锁的等待-System.Threading.SpinWait

如果等待某个条件满足需要的时间很短,而且不希望发生昂贵的上下文切换,那么基于自旋的等待时一种很好的替换方案,SpinWait不仅提供了基本自旋功能,而且还提供了SpinWait.SpinUntil方法,使用这个方法能够自旋直到满足某个条件为止,此外SpinWait是一个Struct,从内存的角度上说,开销很小。SpinLock是对SpinWait的简单封装。

需要注意的是:长时间的自旋不是很好的做法,因为自旋会阻塞更高级的线程及其相关的任务,还会阻塞垃圾回收机制。SpinWait并没有设计为让多个任务或线程并发使用,因此多个任务或线程通过SpinWait方法进行自旋,那么每一个任务或线程都应该使用自己的SpinWait实例。

当一个线程自旋时,会将一个内核放入到一个繁忙的循环中,而不会让出当前处理器时间片剩余部分,当一个任务或者线程调用Thread.Sleep方法时,底层线程可能会让出当前处理器时间片的剩余部分,这是一个大开销的操作。

因此,在大部分情况下,不要在循环内调用Thread.Sleep方法等待特定的条件满足。

下面贴代码,方便大家理解,如有错误请指正:

    class Program
    {
        private static Task[] _CookTasks { get; set; }
        /*定义一个变量 该变量指示是否可以进行下一步操作*/
        private static bool _stepbool = false;
        /*获取当前计算机处理器数*/
        private static int _particpants = Environment.ProcessorCount;
        /*  coder:释迦苦僧   */
        static void Main(string[] args)
        {
            _CookTasks = new Task[_particpants];
            for (int task_index = 0; task_index < _particpants; task_index++)
            {
                _CookTasks[task_index] = Task.Factory.StartNew((num) =>
                {
                    CookStep1();
                    /*等待5秒钟 _stepbool变为true ,如果5秒钟内没有淘好米 则提示超时*/
                    if (!SpinWait.SpinUntil(() => (_stepbool), 1000))
                    {
                        Console.WriteLine("淘个米都花这么长时间....");
                    }
                    else
                    {
                        /*按时淘好米开始煮饭*/
                        Console.WriteLine("淘好米煮饭....");
                    }
                }, task_index);
            }
            /*主线程创造超时条件*/
            Thread.Sleep(3000);
            _stepbool = true;

            Console.ReadLine();
        }
        
        static void CookStep1()
        {
            Console.WriteLine("淘米....");
        }
    }

volatile

volatile关键字能够保证;当这个共享变量被不同线程访问和更新且没有锁和原子操作的时候,最新的值总能在共享变量中表示出来。

volatile变量可以看作是“轻量级lock”。当出于简单编码和可伸缩性考虑时,我们可能会选择使用volatile变量而不是锁机制。某些情况下,如果读操作远多于写操作,也会比锁机制带来更高性能。

volatile变量具有“lock”的可见性,却不具备原子特性。也就是说线程能够自动发现volatile变量的最新值。volatile变量可以实现线程安全,但其应用有限。使用volatile变量的主要原因在于它使用非常简单,至少比使用锁机制要简单的多;其次便是性能原因了,某些情况下,它的性能要优于锁机制。此外,volatile操作不会造成阻塞。

参考:http://www.cnblogs.com/lucifer1982/archive/2008/03/23/1116981.html大家可以看下 写的不错

ManualResetEventSlim

ManualResetEventSlim通过封装手动重置事件等待句柄提供了自旋等待和内核等待的组合。您可以使用这个类的实例在任务直接发送信息,并等待事件的发送。通过信号机制通知任务开始其工作。

其Set 方法将事件状态设置为有信号,从而允许一个或多个等待该事件的线程继续。 其 Wait()方法 阻止当前线程,直到设置了当前 ManualResetEventSlim 为止。

如果需要跨进程或者跨AppDomain的同步,那么就必须使用ManualResetEvent,而不能使用ManualResetEventSlim。

using System;
using System.Threading;
using System.Threading.Tasks;
class MRESDemo
{
    static void Main()
    {
        MRES_SetWaitReset();
    }
    static void MRES_SetWaitReset()
    {
        ManualResetEventSlim mres1 = new ManualResetEventSlim(false);

        var observer = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("阻塞当前线程,使 mres1 处于等待状态...!");
            mres1.Wait();
            while (true)
            {
                if (mres1.IsSet)
                {
                    /*等待 mres1 Set()信号 当有信号时 在执行后面代码*/
                    Console.WriteLine("得到mres1信号,执行后续代码....!");
                }
                Thread.Sleep(100);
            }

        });

        Thread.Sleep(2000);
        Console.WriteLine("取消mres1等待状态");
        mres1.Set();
        Console.WriteLine("当前信号状态:{0}", mres1.IsSet);
        Thread.Sleep(300);
        mres1.Reset();
        Console.WriteLine("当前信号状态:{0}", mres1.IsSet);
        Thread.Sleep(300);
        mres1.Set();
        Console.WriteLine("当前信号状态:{0}", mres1.IsSet);
        Thread.Sleep(300);
        mres1.Reset();
        Console.WriteLine("当前信号状态:{0}", mres1.IsSet);

        observer.Wait();
        mres1.Dispose();
        Console.ReadLine();
    }
}

SemaphoreSlim

有时候,需要对访问一个紫云或者一个资源池的并发任务或者线程的数量做限制时,采用System.Threading.SemaphoreSlim类非常有用。

该了表示一个Windows内核信号量对象,如果等待的时间非常短,System.Threading.SemaphoreSlim类带来的额外开销会更少,而且更适合对任务处理,System.Threading.SemaphoreSlim提供的计数信号量没有使用Windows内核的信号量。

计数信号量:通过跟踪进入和离开任务或线程来协调对资源的访问,信号量需要知道能够通过信号量协调机制所访问共享资源的最大任务数,然后,信号量使用了一个计数器,根据任务进入或离开信号量控制区对计数器进行加减。

需要注意的是:信号量会降低可扩展型,而且信号量的目的就是如此。SemaphoreSlim实例并不能保证等待进入信号量的任务或线程的顺序。

下面贴代码,方便大家理解:

using System;
using System.Threading;
using System.Threading.Tasks;

class MRESDemo
{
    /*code:释迦苦僧*/
    static void Main()
    {
        SemaphoreSlim ss = new SemaphoreSlim(3); // 创建SemaphoreSlim 初始化信号量最多计数为3次
        Console.WriteLine("创建SemaphoreSlim 初始化信号量最多计数为{0}次", ss.CurrentCount);

        // Launch an asynchronous Task that releases the semaphore after 100 ms
        Task t1 = Task.Factory.StartNew(() =>
        {
            while (true)
            {
                /*阻止当前线程,直至它可进入 SemaphoreSlim 为止。*/
                /*阻塞当前任务或线程,直到信号量几首大于0时它才能进入信号量*/
                ss.Wait();
                Console.WriteLine("允许进入 SemaphoreSlim 的线程的数量:{0}", ss.CurrentCount);
                Thread.Sleep(10); 
            }
        });

        Thread.Sleep(3000);
        /*当前Task只能进入3次*/
        /*退出一次信号量  并递增信号量的计数*/
        Console.WriteLine("退出一次信号量  并递增信号量的计数");
        ss.Release();

        Thread.Sleep(3000);
        /*退出3次信号量  并递增信号量的计数*/
        Console.WriteLine("退出三次信号量  并递增信号量的计数");
        ss.Release(3);

        /*等待任务完成*/
        Task.WaitAll(t1); 

        /*释放*/
        ss.Dispose();
        Console.ReadLine();
    }
}

CountdownEvent

有时候,需要对数目随时间变化的任务进行跟踪,CountdownEvent是一个非轻量级的同步原语,与Task.WaitAll或者TaskFactory.ContinueWhenAll 等待其他任务完成执行而运行代码相比,CountdownEvent的开销要小得多。

CountdownEvent实例带有一个初始的信号计数,在典型的fork/join场景下,每当一个任务完成工作的时候,这个任务都会发出一个CountdownEvent实例的信号,并将其信号计数递减1,调用CountdownEvent的wait方法的任务将会阻塞,直到信号计数达到0.

下面贴代码,方便大家理解:

class MRESDemo
{
    /*code:释迦苦僧*/
    static void Main()
    {
        CountdownEvent cde = new CountdownEvent(3); // 创建SemaphoreSlim 初始化信号量最多计数为3次 
        Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}", cde.InitialCount, cde.CurrentCount, cde.IsSet);

        // Launch an asynchronous Task that releases the semaphore after 100 ms
        Task t1 = Task.Factory.StartNew(() =>
        {
            while (true)
            {
                Thread.Sleep(1000);
                if (!cde.IsSet)
                {
                    cde.Signal();
                    Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}", cde.InitialCount, cde.CurrentCount, cde.IsSet);
                }
            }
        }); 
        cde.Wait();
        /*将 CurrentCount 重置为 InitialCount 的值。*/
        Console.WriteLine("将 CurrentCount 重置为 InitialCount 的值。");
        cde.Reset();

        cde.Wait();
        /*将 CurrentCount 重置为 5*/
        Console.WriteLine("将 CurrentCount 重置为 5");
        cde.Reset(5);
        cde.AddCount(2);

        cde.Wait();
        /*等待任务完成*/
        Task.WaitAll(t1);
        Console.WriteLine("任务执行完成");
        /*释放*/
        cde.Dispose();
        Console.ReadLine();
    }
}

class MRESDemo
{
    /*code:释迦苦僧*/
    static void Main()
    {
        CountdownEvent cde = new CountdownEvent(3); // 创建SemaphoreSlim 初始化信号量最多计数为3次 
        Console.WriteLine(" InitialCount={0}, CurrentCount={1}, IsSet={2}", cde.InitialCount, cde.CurrentCount, cde.IsSet);
        /*创建任务执行计数*/
        Task t1 = Task.Factory.StartNew(() =>
        {
            for (int index = 0; index <= 5; index++)
            {
                /*重置计数器*/
                cde.Reset();
                /*创建任务执行计数*/
                while (true)
                {
                    Thread.Sleep(1000);
                    if (!cde.IsSet)
                    {
                        cde.Signal();
                        Console.WriteLine("第{0}轮计数  CurrentCount={1}", index, cde.CurrentCount);
                    }
                    else
                    {
                        Console.WriteLine("第{0}轮计数完成", index);
                        break;
                    }
                }
                /*等待计数完成*/
                cde.Wait();
            }
        });
        t1.Wait();
        /*释放*/
        cde.Dispose();
        Console.ReadLine();
    }
}

«上一页1234下一页»


相关评论