C# NutShell 第二十二章高级线程处理

排它锁

排它锁三种:lock,Mutex,SpinLock

排它锁锁住的是代码块,而不是代码块里面的变量。当别的线程也想要使用这一部分代码时,就需要判断这个锁可不可以用。

lock语句

    class ThreadSafe
    {
        static readonly object _locker = new object();
        static int _val = 0;

        static void Go()
        {
            lock (_locker)
            {
                _val++;
            }
        }
    }

lock语句的本质

        void Fun()
        {
            Monitor.Enter(_locker);
            try
            {
                x++;
            }
            finally
            {
                Monitor.Exit(_locker);
            }
        }

解决上述转换中异常的问题,C#4.0的重载

        bool lockToken = false;
        void Fun()
        {
            Monitor.Enter(_locker, ref lockToken);
            try
            {
                x++;
            }
            finally
            {
                if(lockToken)
                    Monitor.Exit(_locker);
            }
        }

TryEnter,可以指定时间

选择同步对象

1.如果一个对象在各个线程中都是可见的,那就可以用作同步对象。这个对象必须是引用类型的对象

2.同步对象通常是私有的,一般是实例字段或静态字段

3.同步对象本身也是可以被保护的对象

4.锁不会限制同步对象的访问功能

使用锁的时机

需要访问可写的共享字段,需要加锁

锁与原子性

1.如果使用同一个锁对一组变量的读写操作进行保护,可以认为这些变量的读写操作是原子的。

2.在锁中抛出异常会破坏原子操作。可以先将会抛出异常的操作执行,在进行原子操作。或者在finally中实现回滚操作

嵌套锁

只有最外层的lock退出时,对象才会被解除

当一个锁中方法调用另一个方法时用嵌套锁

死锁

        public static void Go1()
        {
            lock (_locker1)
            {
                Console.WriteLine("start thread1");
                Thread.Sleep(1000);
                lock (_locker2)
                {
                    Console.WriteLine("end thread1");
                }
            }
        }

        public static void Go2()
        {
            lock (_locker2)
            {
                Console.WriteLine("start thread2");
                Thread.Sleep(1000);
                lock (_locker1)
                {
                    Console.WriteLine("start thread2");
                }
            }
        }

Mutex

可用于进程锁

一般用于保证只能后运行一个应用程序的实例


锁和线程安全性

1.静态成员是保证线程安全的

2.使用不可变对像,就在定义一个都是readonly字段的对象,这个对象再创建后就不能写了,所以在读取时就没有问题了。


非排他锁

信号量

1.有特定的容量,一旦满员之后就不允许其他人进入了,只有当其中的人离开时,才能允许另外一个人进入

        static SemaphoreSlim _sem = new SemaphoreSlim(3);

        static void Main(string[] args)
        {
            for (int i = 0; i < 6; i++)
            {
                new Thread(Enter).Start(i);
            }


            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Enter(object id)
        {
            Console.WriteLine(id + " wants to enter");
            _sem.Wait();
            Console.WriteLine(id + " is in");
            Thread.Sleep(1000);
            Console.WriteLine(id + " is leave");
            _sem.Release();
        }

2.命名的Semaphore可以跨进程使用

读写锁

1.适用情况,读操作大量(线程安全),写操作少量(线程不安全)

2.一个线程持有写锁,则其他的读操做些操作都被阻断。没有线程有写锁,则所有线程都可以获取写锁和读锁

3.ReaderWriterLockSlim在有线程在读的时候是没办法写的,有线程在写的时候也是没办法读的

4.需要添加try/finally保证在异常时也会释放锁

5.可升级锁EnterUpgradeableReadLock。因为读锁时排他的,所以我们只有在必要的时候才使用写锁。我们先使用读锁,在确定需要修改时,再转换为写锁。

        static void Write()
        {
            while (true)
            {
                _rw.EnterUpgradeableReadLock();
                if (_items.Contains(5))
                {
                    _rw.EnterWriteLock();
                    _items.Add(8);
                    Thread.Sleep(3000);
                    _rw.ExitWriteLock();
                    Thread.Sleep(2000);
                }
                _rw.ExitUpgradeableReadLock();
            }
        }

6.递归锁


使用事件等待句柄发送信号

意思是,我已经蓄势待发,只要闸门一开,就随时可以冲出去了。

AutoResetEvent

1.WaitOne,等待闸门开启

2.Set,开闸门

        //true会直接打开,使线程无需等待
        static EventWaitHandle _waitHandle = new AutoResetEvent(false);
        static EventWaitHandle _waitHandle2 = new EventWaitHandle(false,EventResetMode.AutoReset);

        static void Main(string[] args)
        {
            new Thread(Waiter).Start();
            Thread.Sleep(5000);
            _waitHandle.Set();
            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Waiter()
        {
            Console.WriteLine("Wait...");
            _waitHandle.WaitOne();
            Console.WriteLine("Notified");
        }

3.可以直接先调用Set,这样第一个线程无需任何等待

4.多次调用set无用,闸门只有一道,每次只能通过一人。

5.Reset可使打开的闸门立即关闭

6.双向信号,需要根据对方的执行情况来再次设置闸门

       static EventWaitHandle _waitHandle = new AutoResetEvent(false);
        static EventWaitHandle _waitHandle2 = new EventWaitHandle(false,EventResetMode.AutoReset);

        static void Main(string[] args)
        {
            new Thread(Waiter).Start();
            _waitHandle.WaitOne();
            Thread.Sleep(5000);
            _waitHandle2.Set();

            _waitHandle.WaitOne();
            Thread.Sleep(5000);
            _waitHandle2.Set();

            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Waiter()
        {
            while (true)
            {
                _waitHandle.Set();
                _waitHandle2.WaitOne();
                Thread.Sleep(5000);
            }
        }

ManualResetEvent

开启时,会让任意数量的线程通过。关闭时,没有人能通过,但是都会等待。

        static EventWaitHandle _waitHandle = new ManualResetEvent(false);
        static EventWaitHandle _waitHandle2 = new EventWaitHandle(false,EventResetMode.ManualReset);

        static void Main(string[] args)
        {
            new Thread(Waiter).Start();
            new Thread(Waiter).Start();
            new Thread(Waiter).Start();
            Thread.Sleep(2000);
            _waitHandle.Set();

            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Waiter()
        {
            _waitHandle.WaitOne();
            Console.WriteLine("abc 123");
        }

CountdownEvent

可用于等待多个线程,Signal可以使计数递减,Wait则会阻塞。

在CountdownEvent计数没有减至0时,Wait后面的代码永远不会执行

        static CountdownEvent countdown = new CountdownEvent(3);

        static void Main(string[] args)
        {
            new Thread(Waiter).Start();
            new Thread(Waiter).Start();
            new Thread(Waiter).Start();
            Thread.Sleep(2000);
            countdown.Wait();

            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Waiter()
        {
            Thread.Sleep(2000);
            Console.WriteLine("abc 123");
            countdown.Signal();
        }

EventWaitHandle

支持跨进程操作

等待句柄和延迟操作

不希望阻塞线程,当句柄接到信号或超时时,该附加操作就会执行

        static ManualResetEvent manual = new ManualResetEvent(false);

        static void Main(string[] args)
        {
            RegisteredWaitHandle reg = 
ThreadPool.RegisterWaitForSingleObject(manual, Waiter, "some data", 2000, true);

            Thread.Sleep(5000);
            manual.Set();
            Console.WriteLine("main");
            Console.WriteLine("End");
            Console.ReadLine();
            reg.Unregister(manual);
        }

        static void Waiter(object data, bool timeout)
        {
            Console.WriteLine(data);
        }

WaitAny,WaitAll,SignalAndWait

WaitAny:等待一组句柄中的任意一个句柄

WaitAll:等待一组句柄中的所有句柄

SignalAndWait:先调用一个线程的Set方法,再调用另外一个线程的WaitOne,可以使2个线程在同一时刻交汇


Barrier类

1.允许多个线程在同一时刻汇合

2.指定一个数字,没当调用SignalAndWait的次数到达这个数字之后,才放行。然后再达到这个数字,放行。

        static Barrier barrier = new Barrier(2);

        static void Main(string[] args)
        {
            new Thread(Go).Start();
            new Thread(Go).Start();
            new Thread(Go).Start();
            
            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Go()
        {
            for (int i = 0; i < 3; i++)
            {
                barrier.SignalAndWait();
                Console.WriteLine(i + "Go Go Go");
            }
        }

3.还指定一个后续操作,当放开通行后执行这个操作。这个操作在执行的时候,所有线程都会被阻塞

static Barrier barrier = new Barrier(2, b => Console.WriteLine("sss"));

延迟初始化

Lazy<T>

相当于单例模式中的懒汉模式

        //true表示线程安全,false表示线程不安全(就和正常创建一样)
        Lazy<A> _a = new Lazy<A>(()=> new A(),true);

        public A a { get { return _a.Value; } }

        static void Main(string[] args)
        {
            Console.WriteLine("End");
            Console.ReadLine();
        }

LazyInitializer

原理是,当有多个线程同时创建对象时,直接取第一个创建的对象,多余的对象就不管了。速度很快,但是相应的浪费了CPU

        A _a;

        public A a
        {
            get
            {
                LazyInitializer.EnsureInitialized(ref _a, () => new A());
                return _a;
            }
        }

线程本地存储

使线程有各自的全局变量

ThreadStatic

不好用

        [ThreadStatic]
        static int num = 5;

        static void Main(string[] args)
        {
            new Thread(Go).Start(); //6
            new Thread(Go).Start(); //1
            new Thread(Go).Start(); //1
            new Thread(Go).Start(); //1
            Console.WriteLine(num); //0
            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Go()
        {
            num++;
            Console.WriteLine(num);
        }

ThreadLocal<T>

好用,和预想的一样

        static ThreadLocal<int> num = new ThreadLocal<int>(() => 3);

        static void Main(string[] args)
        {
            new Thread(Go).Start(); 
            new Thread(Go).Start(); 
            new Thread(Go).Start(); 
            new Thread(Go).Start(); 
            Console.WriteLine(num); 
            Console.WriteLine("End");
            Console.ReadLine();
        }

        static void Go()
        {
            num.Value++;
            Console.WriteLine(num.Value);
        }

LocalDataStoreSlot

不怎么好用

        static LocalDataStoreSlot _slot = Thread.GetNamedDataSlot("Secure");

        static int num
        {
            get
            {
                object data = Thread.GetData(_slot);
                return data == null ? 0 : (int)data;
            }
            set
            {
                Thread.SetData(_slot, value);
            }
        } 

Interrupt和Abort方法

可以终止别的线程运行


定时器

多线程定时器

2种都是多线程

        static void Main(string[] args)
        {
            //参数:方法,参数,延迟,循环的时间间隔
            Timer timer = new Timer(Go, "timeeee", 2000, 1000);
            Console.WriteLine("End");
            Thread.Sleep(7000);
            //中断
            timer.Dispose();
            Console.ReadLine();
        }

        static void Go(object state)
        {
            Console.WriteLine(state);
        }

只调用一次,将时间间隔设置为Timeout.Infinite,其实就是-1

        static void Main(string[] args)
        {
            Timer timer = new Timer();
            timer.Interval = 500;
            timer.Elapsed += Go;
            timer.Start();
            timer.Stop();
            Console.ReadLine();
        }

        static void Go(object state, EventArgs arg)
        {
            Console.WriteLine(state);
        }

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值