C#线程同步

一,什么是线程同步和互斥

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
这里的同步千万不要理解成那个同时进行,应是指协同、协助、互相配合。线程同步是指多线程通过特定的设置(如互斥量,事件对象,临界区)来控制线程之间的执行顺序(即所谓的同步)
也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间是各自运行各自的!
在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,
直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步(下文统称为同步)。

如果有多个线程同时访问共享数据的时候,就必须要用线程同步,防止共享数据被破坏。如果多个线程不会同时访问共享数据,可以不用线程同步。
线程同步也会有一些问题存在:
1、性能损耗。获取,释放锁,线程上下文的切换都是耗性能的。
2、同步会使线程排队等待执行。

二,线程同步的方式和机制

临界区(Critical Section)、互斥对象(Mutex):主要用于互斥控制;都具有拥有权的控制方法,只有拥有该对象的线程才能执行任务,所以执行完任务后一定要释放该对象。
信号量(Semaphore)、事件对象(Event):事件对象是以通知的方式进行控制,主要用于同步控制!

三,线程同步的一种方法是加锁(lock)

加锁使多个线程同一时间只有一个线程可以调用该方法,其他线程被阻塞。
同步对象的选择:
1、使用引用类型,值类型加锁时会装箱,产生一个新的对象。
2、使用private修饰,使用public时易产生死锁。(使用lock(this),lock(typeof(实例))时,该类也应该是private)。
3、string不能作为锁对象。
4、不能在lock中使用await关键字
5、如果这个锁是静态类型。这样就是在全局锁定了该方法,不管该类有多少个实例,都要排队执行。
6、如果不使用静态类型的锁,因为被锁定的方法是属于实例的,只要该实例调用锁定方法不产生损坏就可以,不同实例间是不需要锁的。这个锁只锁该实例的方法,而不是锁所有实例的方法.*

class ThreadSafe
{
    private static object _locker = new object();

    void Go()
    {
        lock (_locker)
        {
            ......//共享数据的操作 (Static Method),使用静态锁确保所有实例排队执行
        }
    }

    private object _locker2 = new object();
    void GoTo()
    {
         lock(_locker2)
           ......//共享数据的操作,非静态方法,使用非静态锁,确保同一个实例的方法调用者排队执行
    }
}

收录博文:

1. 并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。其中两种并发关系分别是同步和互斥

2. 互斥:进程间相互排斥的使用临界资源的现象,就叫互斥。

3. 同步:进程之间的关系不是相互排斥临界资源的关系,而是相互依赖的关系。进一步的说明:就是前一个进程的输出作为后一个进程的输入,当第一个进程没有输出时第二个进程必须等待。具有同步关系的一组并发进程相互发送的信息称为消息或事件。其中并发又有伪并发和真并发,伪并发是指单核处理器的并发,真并发是指多核处理器的并发。

4. 并行:在单处理器中多道程序设计系统中,进程被交替执行,表现出一种并发的外部特种;在多处理器系统中,进程不仅可以交替执行,而且可以重叠执行。在多处理器上的程序才可实现并行处理。从而可知,并行是针对多处理器而言的。并行是同时发生的多个并发事件,具有并发的含义,但并发不一定并行,也亦是说并发事件之间不一定要同一时刻发生。

5. 多线程:多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码。多线程可以实现线程间的切换执行。

6. 异步:异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。
异步和多线程并不是一个同等关系,异步是最终目的,多线程只是我们实现异步的一种手段。异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。

为了对以上概念的更好理解举一个简单例子, 

假设我要做 烧开水,举杠铃100下, 洗衣服 3件事情。

烧开水 这件事情, 我要做的事情为, 准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟
举杠铃100下 我要做的事情为, 举杠铃100下 10分钟
洗衣服 我要做的事情为, 准备洗衣服 1分钟, 等衣服洗完 5 分钟 , 关掉洗衣机 1分钟

单核情况下
同步的完成,我需要做的时间为 1+ 8 +1 + 10 + 1+ 5 +1 = 27 分

如果异步,就是在等的时候,我可以切换去做别的事情

准备烧开水(1) + 准备洗衣服(1) + 举50下杠铃 (5)分钟 + 关洗衣机(1)分钟 + 举杠铃20下 (2)分钟 + 关烧水机(1)分钟 + 举30下杠铃(3)分钟
1+1+5+1+2+1+3 =14 分钟

双核 异步 并行
核1 准备烧开水 1分钟 + 举杠铃50下(5)分钟 + 等待3分钟 + 关掉烧水机 1分钟

核2 准备洗衣服 1分钟 + 举杠铃50下(5)分钟 + 关掉洗衣机 1分钟 + 等待3分钟

其实只花了 1+5+3+1 = 10分钟

其中还有双核都等待了3分钟

双核 异步 非并行
核1 举杠铃100下(10)分钟

核2 准备烧开水 1分钟 + 准备洗衣服 1分钟 + 等待5 分钟 + 关掉洗衣机 1分钟 + 等待 1 分钟 + 关掉烧水机 1分钟

其实只花了 1+1+5+1+1+1 = 10分钟

多线程的做法
单核下

线程1 准备烧开水 1分钟, 等开水烧开 8 分钟 , 关掉烧水机 1分钟
线程2 举杠铃100下 10分钟
线程3 准备洗衣服 1分钟, 等开水烧开 5 分钟 , 关掉洗衣机 1分钟

cpu 可能这么切换 最理想的切换方式

线程1 准备烧开水1 sleep 1 sleep 5 sleep 1 sleep 2 关开水 1分钟 exit
线程2 sleep 1 sleep 1 举杠铃50 5分钟 sleep 1 举杠铃20 2分钟 sleep1 举杠铃30下 3分钟 
线程3 sleep 1 准备洗衣服1 分钟 sleep 5 关洗衣机1分钟 exit

最后使用了 14分钟 和 单核异步是一样的。

但是实际上是不一样的,因为线程不会按照我们设想的去跑, 如果线程2 举杠铃先跑,整个流程的速度就下来了。

异步和同步的区别, 在io等待的时候,同步不会切走,浪费了时间。

如果都是独占cpu 的业务, 比如举杠铃的业务, 在单核情况下 多线和单线 没有区别。

多线程的好处是比较容易的实现了异步切换的思想, 因为异步的程序很难写的。

多线程本身还是以同步完成,但是应该说比效率是比不上异步的。 而且多线很容易写, 相对效率也高。

多核的好处,就是可以同时做事情, 这个和单核完全不一样的。

 

posted @ 2018-11-20 11:34  以德为先  阅读(624)  评论(0编辑  收藏  举报