知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂

梯子

一、锁1、lock2、Interlocked3、Monitor4、SpinLock5、Mutex6、Semaphore7、Events1、AutoResetEvent2、ManualResetEvent3、ManualResetEventSlim8、ReaderWriterLock二、线程安全集合三、多线程模型1、同步编程模型SPM2、异步编程模型APM3、基于事件编程模型EAP4、基于任务编程模型TAP四、End

一、锁

数据库中也有锁概念,行锁,表锁,事物锁等,锁的作用就是控制并发情况下数据的安全一致,使一个数据被操作时,其他并发线程等待。开发方面多线程并行编程访问共享数据时,为保证数据的一致安全,有时需要使用锁来锁定对象来达到同步

.NET中提供很多线程同步技术。有lock,Interlocked,Monitor等用于进程内同步锁,Mutex互斥锁,Semaphore信号量,Events,ReaderWriterLockSlim读写锁等用于多个进程间的线程同步

1、lock

lock语句是设置对锁定和解除锁定的一种简单方式,也是最常用的一种同步方式。lock用于锁定一个引用类型字段,当线程执行到Lock处,会锁定该字段,使之只有一个线程进入lock语句块内,才lock语句结束位置再释放锁定,另一个线程才可以进入。原理运用同步块索引,感兴趣可以研究下

lock (obj)
{
    //synchronized region
}

因为只有一个线程可以进去,没有并发,所以牺牲了性能,所以要尽量缩小lock的范围,另一个建议是首选锁一个私有变量,也就是SyncRoot模式,声明一个syncRoot的私有object变量来进行锁定,而不是使用lock(this),因为外面调用者也可能锁定你这个对象的实例,但他并不知道你内部也使用了锁,所以容易造成死锁

private object syscRoot = new object();
public void DoThis()
{
    lock (syscRoot)
    {
        //同一个时间只有一个线程能到达这里
    }
}

2、Interlocked

InterLoacked用于将变量的一些简单操作原子化,也就是线程安全同步。我们常写的i++就不是线程安全的,从内存中取值然后+1然后放回内存中,过程中很可能被其他线程打断,比如在你+1后放回内存时,另一个线程已经先放回去了,也就不同步了。InerLocked类提供了以线程安全的方式递增、递减、交换、读取值的方法
比如以下代替lock的递增方式

int num = 0;
//lock (syscRoot)
//{
//    num++;
//}
num = Interlocked.Increment(ref num);

3、Monitor

上面lock就是Monitor的语法糖,通过编译器编译会生成Monitor的代码,像下面这样

lock (syscRoot)
{
    //synchronized region
}
//上面的lock锁等同于下面Monitor
Monitor.Enter(syscRoot);
try
{
    //synchronized region
}
finally
{
    Monitor.Exit(syscRoot);
}

Monitor不同于Lock就是它还可以设置超时时间,不会无限制的等待下去。

bool lockTaken = false;
Monitor.TryEnter(syscRoot,500,ref lockTaken);
if (lockTaken)
{
    try
    {
        //synchronized region
    }
    finally
    {
        Monitor.Exit(syscRoot);
    }
}
else
{
}

4、SpinLock

SpinLock自旋锁是一种用户模式锁。对了,插一嘴锁分为内核模式锁和用户模式锁,内核模式就是在系统级别让线程中断,收到信号时再切回来继续干活,用户模式就是通过一些cpu指定或则死循环让线程一直运行着直到可用。各有优缺点吧,内核Cpu资源利用率高,但切换损耗,用户模式就相反,如果锁定时间较长,就会白白循环等待,后面就有混合模式锁的出现了

如果有大量的锁定,且锁定时间非常短,SpinLock就很有用,用法和Monitor类似,Enter或TryEnter获取锁,Exit释放锁。IsHeld和IsHeldByCurrentThread指定它当前是否锁定

另外SpinLock是个结构类型,所以注意拷贝赋值时会创建全新副本问题。必要时可按引用来传递

5、Mutex

Mutex互斥锁提供跨多个进程同步一个类,定义互斥锁的时候可以指定互斥锁的名称,这样系统能够识别,所以在另一个进程中定义的互斥,其他进程也是可以访问到的,Mutex.OpenExisting()便可以得到。

bool createdNew = false;
Mutex mutex = new Mutex(false, "ProCharpMutex", out createdNew);
if (mutex.WaitOne())
{
    try
    {
        //synchronized region
    }
    finally
    {
        mutex.ReleaseMutex();
    }
}

介于此我们可以用来禁止一个应用程序启动两次,一般我们通过进程的名称来判断,这里我们使用Mutex实现

bool createdNew = false;
Mutex mutex = new Mutex(false, "SingletonWinAppMutex", out createdNew);
if (!createdNew)
{
    MessageBox.Show("应用程序已经启动过了");
    Application.Exit();
    return;
}

6、Semaphore

Semaphore信号量和互斥类似,区别是,信号量可以同时让多个线程使用,是一种计数的互斥锁定。通过计数允许同时有几个线程访问受保护的资源。也可以指定信号量名称以使在多个进程间共享

Semaphore和上面Mutex都是继承自WaitHandle基类,WaitHandle用于等待一个信号的设置,嗲用Wait,线程会等待接收一个与等待句柄相关的信号

SemaphoreSlim是对Semaphore的轻量替代版本(它不继承WaitHandle),SemaphoreSlim(int initialCount, int maxCount)构造函数可指定最大并发个数,然后在线程内通过SemaphoreSlim的Wait等到直到来接收信号是否可以进去受保护代码块了,最后记得要Release,不然下一个线程获取不到准许进入的信号

7、Events

Events事件锁不同于委托中的事件,在System.Threading命名空间下,用于系统范围内的事件资源的同步,有AutoResetEvent自动事件锁、ManualResetEvent手动事件锁以及轻量版本ManualResetEventSlim

1、AutoResetEvent

AutoResetEvent也是继承自waitHandle类的,也是通过WaitOne来等待直到有信号,它有两种状态:终止和非终止,可以调用set和reset方法使对象进入终止和非终止状态。通俗点就是set有信号,另一个线程可以进入了,reset非终止无信息,其他线程就阻塞了。自动的意思就是一个线程进入了,自动Reset设置无信号了其他线程就进不去了。类似现实中的汽车收费口,一杆一车模式

private AutoResetEvent autoEvent = new AutoResetEvent(false);
public void DoThis()
{
    autoEvent.WaitOne();
    //执行同步代码块
    autoEvent.Set();
}
2、ManualResetEvent

手动事件锁和自动的区别在于,手动事件锁没有信号时会阻塞一批线程的,有信号时,所有线程都运行,同时唤醒多个线程,除非手动Reset再阻塞,类似现实场景中火车道路口的栅栏,落杆拦截一批人,起杆则一批人蜂拥通过,用法和上面一样,WaitOne等待信号,结束时通过Set来通知有信号了,可以通过了

3、ManualResetEventSlim

ManualResetEventSlim通过封装 ManualResetEvent提供了自旋等待和内核等待的混合锁模式。如果需要跨进程或者跨AppDomain的同步,那么就必须使用ManualResetEvent。ManualResetEventSlim使用Wait来阻塞线程,支持任务的取消。和SemaphoreSlim的Wait一样,内部先通过用户模式自旋然后再通过内核模式效率更高

8、ReaderWriterLock

ReaderWriterLock读写锁不是从限定线程个数的角度来保护资源,而是按读写角度来区分,就是你可以锁定当某一类线程(写线程)中一个进入受保护资源时,另一类线程(读线程)全部阻塞。如果没有写入线程锁定资源,就允许多个读取线程方法资源,但只能有一个写入线程锁定该资源

具体用法参考示例

// 创建读写锁
ReaderWriterLock rwLock = new ReaderWriterLock();
// 当前线程获取读锁,参数为:超时值(毫秒)
rwLock.AcquireReaderLock(250);
// 判断当前线程是否持有读锁
if (!rwLock.IsReaderLockHeld)
{
    return;
}
Console.WriteLine("拿到了读锁......");
// 将读锁升级为写锁,锁参数为:超时值(毫秒)
LockCookie cookie = rwLock.UpgradeToWriterLock(250);
// 判断当前线程是否持有写锁
if (rwLock.IsWriterLockHeld)
{
    Console.WriteLine("升级到了写锁......");
    // 将锁还原到之前所的级别,也就是读锁
    rwLock.DowngradeFromWriterLock(ref cookie);
}
// 释放读锁(减少锁计数,直到计数达到零时,锁被释放)
rwLock.ReleaseReaderLock();
Console.WriteLine("顺利执行完毕......"); // 当前线程获取写锁,参数为:超时值(毫秒)
rwLock.AcquireWriterLock(250);
// 判断当前线程是否持有写锁
if (rwLock.IsWriterLockHeld)
{
    Console.WriteLine("拿到了写锁......");
    // 释放写锁(将减少写锁计数,直到计数变为零,释放锁)
    rwLock.ReleaseWriterLock();
}
// 释放写锁(将减少写锁计数,直到计数变为零,释放锁)
// 当前线程不持有锁,会抛出异常
rwLock.ReleaseWriterLock();
Console.WriteLine("顺利执行完毕......");
Console.ReadLine();

ReaderWriterLockSlim同样是ReaderWriterLock的轻量优化版本,简化了递归、升级和降级锁定状态的规则。
1. EnterWriteLock 进入写模式锁定状态
2. EnterReadLock 进入读模式锁定状态
3. EnterUpgradeableReadLock 进入可升级的读模式锁定状态
并且三种锁定模式都有超时机制、对应 Try… 方法,退出相应的模式则使用 Exit… 方法,而且所有的方法都必须是成对出现的

二、线程安全集合

并行环境下修改共享变量为了保证资源安全,通常使用上面介绍的锁或信号量来解决此问题。其实.NET也内置了一些线程安全的集合,使用他们就像使用单线程集合一样。

类型 描述
BlockingCollection 提供针对实现 IProducerConsumerCollection 的任何类型的限制和阻塞功能。 有关详细信息,请参阅BlockingCollection 概述。
ConcurrentDictionary<tkey,tvalue style="font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;"> 键/值对字典的线程安全实现。
ConcurrentQueue FIFO(先进先出)队列的线程安全实现。
ConcurrentStack LIFO(后进先出)堆栈的线程安全实现。
ConcurrentBag 无序的元素集合的线程安全实现。
IProducerConsumerCollection 类型必须实现以在 BlockingCollection 中使用的接口。

三、多线程模型

1、同步编程模型SPM

2、异步编程模型APM

我们常见的XXBegin, XXEnd这两个经典的配对方法就是异步的,Begin后会委托给线程池调用一个线程去执行。还有委托的BeginInvoke调用

FileStream fs = new FileStream("D:\\test.txt", FileMode.Open);
var bytes = new byte[fs.Length];
fs.BeginRead(bytes, 0, bytes.Length, (aysc) =>
{
    var num = fs.EndRead(aysc);
}, string.Empty);

3、基于事件编程模型EAP

WinFrom/WPF开发中的BackgroundWorker类就是异步事件模式的一种实现方案,RunWorkerAsync方法启动与DoWork事件异步关联的方法,工作完成后,就触发RunWorkerCompleted事件,也支持CancelAysnc方法取消以及ReportProgress通知进度等。还又一个典型的就是WebClient

WebClient client = new WebClient();
client.DownloadDataCompleted += (sender,e)=> 
{
};
client.DownloadDataAsync(new Uri("https://www.baidu.com/"));

4、基于任务编程模型TAP

Task出来后,微软就大力推广基于Task的异步编程模型,APM和EAP都被包装成Task使用。下面示例简单用Task封装上面的编程模型。WebClient的DownloadDataTaskAsync实现和示例中的类似,利用一个TaskCompletionSource包装器包装成Task

FileStream fs = new FileStream("D:\\test.txt", FileMode.Open);
var bytes = new byte[fs.Length];
var task = Task.Factory.FromAsync(fs.BeginRead, fs.EndRead, bytes, 0, bytes.Length, string.Empty);
var nums = task.Result; Action action = () =>{ };
var task = Task.Factory.FromAsync(action.BeginInvoke, action.EndInvoke, string.Empty); public static Task<int> GetTaskAsuc(string url)
{
    TaskCompletionSource<int> source = new TaskCompletionSource<int>();//包装器
    WebClient client = new WebClient();
    client.DownloadDataCompleted += (sender, e) =>
    {
        try
        {
            source.TrySetResult(e.Result.Length);
        }
        catch (Exception ex)
        {
            source.TrySetException(ex);
        }
    };
    client.DownloadDataAsync(new Uri(url));
    return source.Task;
}

四、End

最近几篇介绍了如何编写多线程和多任务应用程序。在应用程序开发过程中要仔细规划,太多的线程导致资源问题,太少则起不到大效果。多线程编程中一个中肯的建议就是
尽量避免修改共享变量,使同步的要求变低。通过合理规划可以减少大部分的同步复杂度。

Search the fucking web
Read the fucking maunal

——GoodGoodStudy

.NET进阶篇06-async异步、thread多线程4的更多相关文章

  1. 02: tornado进阶篇

    目录:Tornado其他篇 01: tornado基础篇 02: tornado进阶篇 03: 自定义异步非阻塞tornado框架 04: 打开tornado源码剖析处理过程 目录: 1.1 自定制t ...

  2. C#异步和多线程以及Thread、ThreadPool、Task区别和使用方法

    本文的目的是为了让大家了解什么是异步?什么是多线程?如何实现多线程?对于当前C#当中三种实现多线程的方法如何实现和使用?什么情景下选用哪一技术更好? 第一部分主要介绍在C#中异步(async/awai ...

  3. c# 异步( Async ) 不是多线程

    c# 异步( Async ) 不是多线程   误解 async 在调试 xxxxAsync() 方法的时候,常常会看到调试器界面中会多出一些线程,直觉上误认为 Async 冠名的函数是多线程. 对于 ...

  4. spring boot(17)-@Async异步

    验证码的异步机制 上一篇讲过可以用邮件发验证码,通常我们在某网站发验证码时,首先会提示验证码已发送,请检查邮箱或者短信,这就是图中的1和3.然而此时查看邮箱或短信可能并没有收到验证码,往往要过几秒种才 ...

  5. PowerBuilder编程新思维2:嵌入(Thread多线程)

    PowerBuilder编程新思维2:嵌入(Thread多线程) 在PB中使用多线程,在网上有大量的文章介绍.不过深入研究并试着给出更易用的模型的,目前还只有"路人甲cw"的一篇& ...

  6. Node.js进阶篇-koa、钩子函数、websocket、嵌入式开发

    代码地址如下:http://www.demodashi.com/demo/12932.html 一.简介     koa是由Express原班人马打造的,致力于成为一个更小.更富有表现力.更健壮的We ...

  7. 在 .NET 4.0 下编写扩展代码以支持 async 异步编程

    微软在C# 5中引入了async.await这两个异步编程的关键字,要使用这两个关键字需要你的IDE支持C#5.0语法,也就意味着你需要使用VS 2012版本以上IDE,或者在Vs2010卸载其编译器 ...

  8. Oracle RMAN 学习:演练进阶篇

    Oracle RMAN 学习:演练进阶篇 5 Rman备份演练进阶篇 5.1 是否选择增量备份 Backup命令生成的备份集中只备份了那些使用了的数据块,备份集实际大小已经较目标数据库的数据文件小了很 ...

  9. Spring Boot (18) @Async异步

    通常我们在某网站发送邮件验证码时,首先会提示验证码已发送,然而此时可能没有收到验证码,过几秒种才真正的收到.如果是同步会先验证发送是否成功然后再通知,如果是异步可以先通知用户已发送,并释放请求,然后再 ...

随机推荐

  1. Python3爬虫(2)_利用urllib.urlopen发送数据获得反馈信息

    一.urlopen的url参数 Agent url不仅可以是一个字符串,例如:https://baike.baidu.com/.url也可以是一个Request对象,这就需要我们先定义一个Reques ...

  2. 使用Bind提供域名解析服务(正向解析)

    小知识: 一般来讲域名比IP地址更加的有含义.也更容易记住,所以通常用户更习惯输入域名来访问网络中的资源,但是计算机主机在互联网中只能通过IP识别对方主机,那么就需要DNS域名解析服务了. DNS域名 ...

  3. Netty学习篇④-心跳机制及断线重连

    心跳检测 前言 客户端和服务端的连接属于socket连接,也属于长连接,往往会存在客户端在连接了服务端之后就没有任何操作了,但还是占用了一个连接:当越来越多类似的客户端出现就会浪费很多连接,netty ...

  4. IPv6笔记-地址结构与分类

    1.地址基础 IPv6地址由被划分为8个16位块的128位组成. 然后将每个块转换为由冒号符号分隔的4位十六进制数字. 2001::3238:00E1:0063:0000:0000:FEFB 每一块多 ...

  5. Vue的指令以及组件化开发

    一. 自定义指令 如何: 1. 创建指令 Vue.directive("指令名",{ inserted(elem){//指令所在的元素被加载到DOM树上后自动执行指令 //elem ...

  6. docker监控容器

    Weave Scope: 是能够自动生成一张 Docker 容器web动态图的监控软件,能够让我们直观地理解.监控和控制容器. 监控一台主机: 第一步:安装 [root@localhost ~]# c ...

  7. F#周报2019年第46期

    新闻 使用Pulumi和.NET Core创建现代云应用 宣告.NET Core 3.1预览版3 ML.NET模型构建器升级 .NET Framework修复工具 Mac上的Visual Studio ...

  8. glsl shader简明教程系列1

    glsl shader简明教程系列1 底层的东西我就不说了(自己去百度翻基础教程)  我直接说上层了(片段着色器) web编辑器还在开发中 有了编辑器 到时候可以把代码复制上去可以看到效果了 1  实 ...

  9. Git II: 操作远程Repository基础

    很久之前写过一篇Git: Setup a remote Git repository,留意到有前同事谈论到Git的一些操作,就把Git值得留意的操作补补全吧.这次,主要讲述Git远程Repositor ...

  10. ASP.NET Core 1.0: Using Entity Framework Core 1.0 - Transaction

    跟Entity Framework之前的版本不同,Class DbContext不再有AcceptAllChanges()方法. 使用Transaction需要使用DbContext中的Databas ...