多线程与线程锁

参考转载自:.NET多线程 - Broder - 博客园 (cnblogs.com)

一、进程和线程定义

进程:一个程序在服务器上运行时,占用的计算机资源合集,就是进程。

线程:是程序能够独立运行的最小单位。线程具有进程所具有的特征,所以线程又叫轻型进程

二、多线程

线程分为原生线程和托管线程,原生线程生命周期由操作系统管理,托管线程由.net程序管理

线程分为前台线程和后台线程,后台线程并非程序不可或缺的一部分。反之,如果所有前台进程都结束,程序也将结束,所有后台线程也将被杀死。

三、启动线程的方式

Thread的缺陷

  1. 官方提供了丰富的API,Thread操纵的是托管线程,然后对CPU发出指令,操控原生线程。这就导致响应不灵敏,无法很好的控制线程。
  2. Thread对启动线程数量不设控制,如果使用不当,会造成死机。       

ThreadPool缺陷

        1.提供的API太少了,无法操控线程。虽然可以使用ManualResetEvent来进行阻塞和恢复线程,但是操作还是不方便。

Task

1.类 Task 表示一个不返回值且通常异步执行的单个操作

2.派生类 Task 表示返回值且通常异步执行的单个操作

3.Task的工作通常以异步方式在线程池线程上执行

Task的构建

实例化对象


 
Task task = new Task(() =>
     {
          Console.WriteLine("");
     });
task.Start();//开启了一个新的线程
使用Run方法


 
Task.Run(() =>
{
   Console.WriteLine("");
});
使用Factory工厂


 
TaskFactory taskFactory = Task.Factory;
taskFactory.StartNew(() =>
{
     Console.WriteLine("");
});
使用Delay创建在指定的毫秒数后完成的任务


 
Task task = Task.Delay(2000).ContinueWith(t =>  //任务在2000ms 之后执行
{
     Console.WriteLine("");
});

 线程异常

使用AggregateException类型异常

 
try {
    Task.WaitAll(tasks);
}
catch (AggregateException ae) {
    Console.WriteLine("One or more exceptions occurred:");
    foreach (var ex in ae.InnerExceptions)
        Console.WriteLine("{0}: {1}", ex.GetType().Name, ex.Message);
}

 线程取消

通常情况下,多线程中一个线程出现异常,其它的线程任务就不用进行了,我们如何取消线程

代码演示

 
  /// <summary>
        /// 取消线程
        /// </summary>
        public static void CancelThreadDemo()
        {
            CancellationTokenSource cts = new CancellationTokenSource();// 通知式的
            try
            {
                List<Task> taskList = new List<Task>();
                for (int i = 0; i < 100; i++)
                {
                    string name = $"btnThreadCore_Click_{i}";
                    int k = i;
                    taskList.Add(Task.Run(() =>
                    {
                        if (k == 5)
                        {
                            throw new Exception($"{name} 异常了");
                        }
                        if (!cts.IsCancellationRequested)//是否取消
                        {
                            Console.WriteLine($"this is {name} Ok!");
                        }
                        else
                        {
                            Console.WriteLine($"this is {name} Stop!");
                        }
                    }));
                };
                Task.WaitAll(taskList.ToArray());
            }
            catch (AggregateException aex)  //能够有多个Catch 在匹配异常类型的时候,先具体,而后在寻找父类
            {
                cts.Cancel(); //执行该方法之后,IsCancellationRequested会被指定为false
                foreach (var exception in aex.InnerExceptions)
                {
                    Console.WriteLine(exception.Message);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw;
            }
        }

 Task的属性

CompletedTask获取一个已成功完成的任务。
CurrentId 返回当前正在执行 Task 的 ID。
Exception 获取导致 AggregateException 提前结束的 Task。 如果 Task 成功完成或尚未引发任何异常,这将返回 null
Factory 提供对用于创建和配置 Task 和 Task 实例的工厂方法的访问。
IsCompleted 获取一个值,它表示是否已完成任务。

 

Task的方法

Start()启动 Task,并将它安排到当前的 TaskScheduler 中执行。
Run(Action) 将在线程池上运行的指定工作排队,并返回代表该工作的 Task 对象。
Delay(Int32) 创建一个在指定的毫秒数后完成的任务。
ContinueWith(Action) 创建一个在目标 Task 完成时异步执行的延续任务。
Dispose() 释放 Task 类的当前实例所使用的所有资源。
Wait() 等待 Task 完成执行过程。
WhenAny(Task[]) 非阻塞,任何提供的任务已完成时,创建将完成的任务。
WhenAll(Task[]) 非阻塞,创建一个任务,该任务将在数组中的所有 Task 对象都已完成时完成。
WaitAny(Task[]) 阻塞线程,等待提供的任一 Task 对象完成执行过程。
WaitAll(Task[]) 阻塞线程,等待提供的所有 Task 对象完成执行过程。

四、线程同步(线程锁)

定义:使并发执行的多个线程能够按照一定的规则(时序)共享系统资源。即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作

1、Interlocked

    /// <summary>
    /// 线程锁
    /// </summary>
    public class Sample05
    {
        //0 未使用, 1 正在使用.
        private static int usingResource = 0;
        private const int numThreads = 10;
        private const int numThreadIterations = 5;
        

        public static void InterLockedDemo()
        {
            //var result1 = Interlocked.Exchange(ref usingResource, 5);
            //var result2 = Interlocked.CompareExchange(ref usingResource, 10, 5);

            Thread myThread;
            Random rnd = new Random();
            //开启了10个线程
            for (int i = 0; i < numThreads; i++)
            {
                myThread = new Thread(new ThreadStart(MyThreadProc));
                myThread.Name = String.Format("Thread{0}", i + 1);
                Thread.Sleep(rnd.Next(0, 1000));
                myThread.Start();
            }
        }

        private static void MyThreadProc()
        {
            for (int i = 0; i < numThreadIterations; i++)
            {
                UseResource();
                Thread.Sleep(1000);
            }
        }

        static bool UseResource()
        {
            //判断资源是否被占用     
            if (0 == Interlocked.Exchange(ref usingResource, 1))
            {
                Console.WriteLine("{0} 获取锁", Thread.CurrentThread.Name);

                Thread.Sleep(500);

                Console.WriteLine("{0} 释放锁", Thread.CurrentThread.Name);

                //释放锁
                Interlocked.Exchange(ref usingResource, 0);
                return true;
            }
            else
            {
                Console.WriteLine("   {0} 获取锁失败", Thread.CurrentThread.Name);
                return false;
            }
        }
    }

 

2、自旋锁

自旋锁:自旋锁(Spinlock)是最简单的线程锁,基于原子操作实现

.NET提供对象

SpinWait

SpinLock

    /// <summary>
    /// 自旋锁
    /// </summary>
    internal class Sample06
    {
        //0 未使用, 1 正在使用.
        private static int _lock = 0;

        public static void SpinWaitDemo()
        {
            //var spinWait = new SpinWait();
            while (Interlocked.Exchange(ref _lock, 1) != 0)
            {
                Thread.SpinWait(1);
                //spinWait.SpinOnce();//(推荐)
                // 一定次数以内,核心大于1,Thread.SpinWait
                // 超过一定次数,核心等于1,交替使用Thread.Sleep(0)和Thread.Yield方法
                // 再超过一定次数,Thread.Sleep(1)

                // Sleep(0)实际上调用SleepEx系统函数
                // Yield()调用SwitchToThread的系统函数
            }
            {
                /*锁保护区
                  方法体*/
            }
            Interlocked.Exchange(ref _lock, 0);
        }

        /// <summary>
        /// SpinLock用法
        /// </summary>
        private static SpinLock _spinLock = new SpinLock();
        public static void SpinLockDemo()
        {
            bool lockTaken = false;
            try
            {
                _spinLock.Enter(ref lockTaken);
                {
                    /*锁保护区
                    方法体*/
                }
            }
            finally
            {
                if (lockTaken)
                {
                    _spinLock.Exit();
                }
            }
        }

    }

优点

  • 避免了上下文切换,效率高

缺点:

  • 方法体不适用于长时间运行的操作,不然会影响其它线程运行
  • 当前实现没有考虑到公平性,如果多个线程同时获取锁失败,按时间顺序第一个获取锁的线程不一定会在释放锁后第一个获取成功

3 互斥锁

基于原子操作线程调度方式来实现。

互斥锁->是否被获取->获取失败,不进行重试->进入等待队列

锁释放->查看等待队列中是否存在线程->唤醒等待线程->调度运行(比自旋慢很多)

.NET提供对象

Mutex

internal class Sample07
    {
        private static readonly Mutex _lock = new Mutex();
        /// <summary>
        /// 互斥锁简单使用
        /// </summary>
        public static void MutexDemo()
        {
            _lock.WaitOne();
            try
            {
                {
                    /*锁保护区*/
                    /*方法体*/
                }
            }
            catch (AggregateException ae)
            {
                throw ae;
            }
            finally
            {
                _lock.ReleaseMutex();//释放锁
                //如果锁不在使用,立即释放资源
                _lock.Dispose();
            }
        }
    }

优点

  • 支持跨进程,防止程序多开、共享系统资源
  • 支持冲入,实现递归锁

缺点

  • 效率低

 

4 混合锁(Lock)

混合锁的特征是在获取锁失败后像自旋锁一样重试一定的次数,超过一定次数之后(.NET Core 2.1 是30次)再安排当前进程进入等待状态

.NET提供对象

Monitor

Lock

锁的对象

通常我们都会锁私有的引用对象,锁的是对象的内存引用地址

严格地说,提供给 lock 的对象只是用来唯一地标识由多个线程共享的资源,所以它可以是任意类实例。

  1. public,会导致死锁或者锁竞争
  2. string, 由于string具有不变性和字符串驻留,意味着整个程序中给定字符串只有一个实例。
internal class Sample08
    {
        private static readonly object Lock = new object();
        public static void MonitorDemo() {

            var lockObj = Lock;
            var lockTaken = false;
            try
            {
                // 获取锁
                Monitor.Enter(lockObj, ref lockTaken);

                // 锁保护
                {
                    //方法体
                }
            }
            finally
            {
                // 释放锁
                if (lockTaken) Monitor.Exit(lockObj);
            }
        }
    }

5. 读写锁

ReaderWriterLock

定义支持单个写线程和多个读线程的锁。该锁的作用主要是解决并发读的性能问题,使用该锁,可以大大提高数据并发访问的性能,只有在写时,才会阻塞所有的读锁。

读与写互斥,两种状态1、同时有多个线程读 2、一个线程写

private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
private static void WriteDemo()
{
    // 获取写入锁
    Lock.EnterWriteLock();
    try
    {
        {
            //IO操作
        }
    }
    catch (Exception ex)
    {             
    }
    finally
    {
        // 释放写入锁
        Lock.ExitWriteLock();
    }
}

 

posted @ 2024-04-07 19:34  DaiWK  阅读(20)  评论(0编辑  收藏  举报