详解C#多线程之线程同步

多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程;其二是线程同步问题,鄙人现在学习与探究的是线程同步问题。

通过学习《CLR via C#》里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造。所谓基元则是在代码中使用最简单的构造。基原构造又分成两类,一个是用户模式,另一个是内核模式。而混合构造则是在内部会使用基元构造的用户模式和内核模式,使用它的模式会有一定的策略,因为用户模式和内核模式各有利弊,混合构造则是为了平衡两者的利与弊而设计出来。下面则列举整个线程同步体系结构

基元

1.1 用户模式

1.1.1 volatile

1.1.2 Interlock

1.2 内核模式

1.2.1 WaitHandle

1.2.2 ManualResetEvent与AutoResetEvent

1.2.3 Semaphore

1.2.4 Mutex

混合

2.1 各种Slim

2.2 Monitor

2.3 MethodImplAttribute与SynchronizationAttribute

2.4 ReaderWriterLock

2.5 Barier(少用)

2.6 CoutdownEvent(少用)

先从线程同步问题的原因说起,当内存中有一个整形的变量A,里面存放的值是2,当线程1执行的时候它会把A的值从内存中取出存放到CPU的寄存器中,并把A赋值为3,此时刚好线程1的时间片结束;接着CPU把时间片分给线程2,线程2同样把A从内存中的值取出来放到内存中,但是由于线程1并没有把变量A的新值3放回内存,故线程2读到的仍然是旧的值(也就是脏数据)2,然后线程2要是需要对A值进行一些判断之类的就会出现一些非预期的结果了。

而针对上面这种对资源的共享问题处理,往往会使用各种各样办法。下面则逐一介绍

先说说基元构造中的用户模式,凡是用户模式的优点是它的执行相对较快,因为它是通过一系列CPU指令来协调,它造成的阻塞只是极短时间的阻塞,对操作系统而言这个线程是一直在运行,从未被阻塞。缺点就是唯有系统内核才能停止这样的一个线程运行。另一方面就是由于线程在自旋而非阻塞,那么它还会占用这CPU的时间,造成对CPU时间的浪费。

首先是基元用户模式构造中的volatile构造,这个构造网上很多说法是让CPU对指定字段(Field,也就是变量)的读都是从内存读,每次写都是往内存写。然而它和编译器的代码优化有关系。先看看如下代码

public class StrageClass
 {
 vo int mFlag = 0;
 int mValue = 0;
 public void Thread1()
 {
  mValue = 5;
  mFlag = 1;
 }
 public void Thread2()
 {
  if (mFlag == 1)
  Console.WriteLine(mValue);
 }
 }

在懂得多线程同步问题的同学们都会知道如果用两个线程分别去执行上面两个方法时,得出的结果有两个:

1.不输出任何东西;

2.输出5。但是在CSC编译器编译成IL语言或JIT编译成机器语言的过程中,会进行代码优化,在方法Thread1中,编译器会觉得给两个字段赋值会没什么所谓,它只会站在单个线程执行的角度来看,完全不会顾及多线程的问题,因此它有可能会把两行代码的执行顺序调乱,导致先给mFlag赋值为1,再给mValue赋值为5,这就导致了第三种结果,输出0。可惜这种结果我一直无法测试出来。

解决这个现象的就是volatile构造,使用了这种构造的效果是,凡是对使用了此构造的字段进行读操作时,该操作都保证在原有代码顺序下会在最先执行;或者是凡是对使用了此构造的字段进行写操作时,该操作都保证在原有代码顺序下会在最后执行。

实现了volatile的构造现在来说有三个,其一是Thread的两个静态方法VolatileRead和VolatileWrite,在MSND上的解析如下

Thread.VolatileRead 读取字段值。 无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。

Thread.VolatileWrite 立即向字段写入一个值,以使该值对计算机中的所有处理器都可见。

在多处理器系统上, VolatileRead 获得由任何处理器写入的内存位置的最新值。 这可能需要刷新处理器缓存;VolatileWrite 确保写入内存位置的值立即可见的所有处理器。 这可能需要刷新处理器缓存。

即使在单处理器系统上, VolatileRead 和 VolatileWrite 确保值为读取或写入内存,并不缓存 (例如,在处理器寄存器中)。 因此,您可以使用它们可以由另一个线程,或通过硬件更新的字段对访问进行同步。

从上面的文字看不出他和代码优化有任何关联,那接着往下看。

volatile关键字则是volatile构造的另外一种实现方式,它是VolatileRead和VolatileWrite的简化版,使用 volatile 修饰符对字段可以保证对该字段的所有访问都使用 VolatileRead 或 VolatileWrite。MSDN中对volatile关键字的说明是

volatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。

从这里可以看出跟代码优化有关系了。而纵观上面的介绍得出两个结论:

1.使用了volatile构造的字段读写都是直接对内存操作,不涉及CPU寄存器,使得所有线程对它的读写都是同步,不存在脏读了。读操作是原子的,写操作也是原子的。

2.使用了volatile构造修饰(或访问)字段,它会严格按照代码编写的顺序执行,读操作将会在最早执行,写操作将会最迟执行。

最后一个volatile构造是在.NET Framework中新增的,里面包含的方法都是Read和Write,它实际上就相当于Thread的VolatileRead 和VolatileWrite 。这需要拿源码来说明了,随便拿一个Volatile的Read方法来看

而再看看Thraed的VolatileRead方法

另一个用户模式构造是Interlocked,这个构造是保证读和写都是在原子操作里面,这是与上面volatile最大的区别,volatile只能确保单纯的读或者单纯的写。

为何Interlocked是这样,看一下Interlocaked的方法就知道了

Add(ref int,int)// 调用ExternAdd 外部方法
CompareExchange(ref Int32,Int32,Int32)//1与3是否相等,相等则替换2,返回1的原始值
Decrement(ref Int32)//递减并返回 调用add
Exchange(ref Int32,Int32)//将2设置到1并返回
Increment(ref Int32)//自增 调用add

就随便拿其中一个方法Add(ref int,int)来说(Increment和Decrement这两个方法实际上内部调用了Add方法),它会先读到第一个参数的值,在与第二个参数求和后,把结果写到给第一参数中。首先这整个过程是一个原子操作,在这个操作里面既包含了读,也包含了写。至于如何保证这个操作的原子性,估计需要查看Rotor源码才行。在代码优化方面来说,它确保了所有写操作都在Interlocked之前去执行,这保证了Interlocked里面用到的值是最新的;而任何变量的读取都在Interlocked之后读取,这保证了后面用到的值都是最新更改过的。

CompareExchange方法相当重要,虽然Interlocked提供的方法甚少,但基于这个可以扩展出其他更多方法,下面就是个例子,求出两个值的最大值,直接抄了Jeffrey的源码

查看上面代码,在进入循环之前先声明每次循环开始时target的值,在求出最值之后,核对一下target的值是否有变化,如果有变化则需要再记录新值,按照新值来再求一次最值,直到target不变为止,这就满足了Interlocked中所说的,写都在Interlocked之前发生,Interlocked往后就能读到最新的值。

基元内核模式

内核模式则是靠操作系统的内核对象来处理线程的同步问题。先说其弊端,它的速度会相对慢。原因有两个,其一由于它是由操作系统内核对象来实现的,需要操作系统内部去协调,另外一个原因是内核对象都是一些非托管对象,在了解了AppDomain之后就会知道,访问的对象不在当前AppDomain中的要么就进行按值封送,要么就进行按引用封送。经过观察这部分的非托管资源是按引用封送,这就会存在性能影响。综合上面两方面的两点得出内核模式的弊端。但是他也是有利的方面:1.线程在等待资源的时候不会"自旋"而是阻塞,这个节省了CPU时间,并且这个阻塞可以设定一个超时值。2.可以实现Window线程和CLR线程的同步,也可同步不同进程中的线程(前者未体验到,而对于后者则知道semaphores中有边界值资源)。3.可应用安全性设置,为经授权账户禁止访问(这个不知道是咋回事)。

内核模式的所有对象的基类是WaitHandle。内核模式的所有类层次如下

WaitHandle

EventWaitHandle

AutoResetEvent

ManualResetEvent

Semaphore

Mutex

WaitHandle继承MarshalByRefObject,这个就是按引用封送了非托管对象。WaitHandle里面主要是各种Wait方法,调用了Wait方法在没有收到信号之前会被阻塞。WaitOne则是等待一个信号,WaitAny(WaitHandle[] waitHandles)则是收到任意一个waitHandles的信号,WaitAll(WaitHandle[] waitHandles)则是等待所有waitHandles的信号。这些方法都有一个版本允许设置一个超时时间。其他的内核模式构造都有类似的Wait方法。

EventWaitHandle的内部维护着一个布尔值,而Wait方法会在这个布尔值为false时线程就会被阻塞,直到该布尔值为true时线程才被释放。操纵这个布尔值的方法有Set()和Reset(),前者是把布尔值设成true;后者则设成false。这相当于一个开关,调用了Reset之后线程执行到Wait就暂停了,直到Set才恢复。它有两个子类,使用的方式类似,区别在于AutoResetEvent调用Set之后自动调用Reset,使得开关马上恢复关闭状态;而ManualResetEvent就需要手动调用Set让开关关闭。这样就达到一个效果一般情况下AutoResetEvent每次释放的时候能让一条线程通过;而ManualResetEvent在手动调用Reset之前有可能会让多条线程通过。

Semaphore的内部是维护着一个整形,当构造一个Semaphore对象时会指定最大的信号量与初始信号量值,每当调用一次WaitOne,信号量就会加1,当加到最大值时,线程就会被阻塞,当调用Release的时候就会释放一个或多个信号量,此时被阻塞掉的一个或多个线程就会被释放。这个就符合生产者与消费者问题了,当生产者不断往产品队列中加入产品时,他就会WaitOne,当队列满了,就相当于信号量满了,生成者就会被阻塞,当消费者消费掉一个商品时,就会Release释放掉产品队列中的一个空间,此时因没有空间存放产品的生产者又可以开始工作往产品队列中存放产品了。

Mutex的内部与规则相对前面两者稍微复杂一点,先说与前面相似的地方就是同样都会通过WaitOne来阻塞当前线程,通过ReleastMutex来释放对线程的阻塞。区别在于WaitOne的允许第一个调用的线程通过,其余后面的线程调用到WaitOne就会被阻塞,通过了WaitOne的线程可以重复调用WaitOne多次,但是必须调用同样次数的ReleaseMutex来释放,否则会因为次数不对等导致别的线程一直处于阻塞的状态。相比起之前的几个构造,这个构造会有线程所有权与递归这两个概念,这个是单纯靠前面的构造都无法实现的,额外封装除外。

混合构造

上面的基元构造是用了最简单的实现方式,用户 模式有用户模式的快,但是它会带来CPU时间的浪费;内核模式解决了这个问题,但是会带来性能上的损失,各有利弊,而混合构造则是集合了两者的利,它会在内部通过一定策略适当的时机使用用户模式,再另一种情况下又会使用内核模式。但是这些层层判断带来的是内存上的开销。在多线程同步中没有完美的构造,各个构造都有利弊,存在即有意义,结合具体的应用场景就会有最优的构造可供使用。只是在于我们能否按照具体的场景权衡利弊而已。

各种Slim后缀的类,在System.Threading命名空间中,可以看到若干个以Slim后缀结尾的类:ManualResetEventSlim,SemaphoreSlim,ReaderWriterLockSlim。除了最后一个,其余两个都是在基元内核模式中有一样的构造,但是这三个类都是原有构造的简化版,尤其是前两个,使用方式跟原有的一样,但是尽量避免使用操作系统的内核对象,而达到了轻量级的效果。比如在SemaphoreSlim中使用了内核构造ManualResetEvent,但是这个构造是通过延时初始化,没达到非不得已时都不使用。至于ReaderWriterLockSlim则在后面再介绍。

Monitor与lock,lock关键字可谓是最广为人知的一种实现多线程同步的手段,那么下面则又从一段代码说起

这个方法相当简单且无实际意义,它只是为了看编译器把这段代码编译成什么样子,通过查看IL如下

留意到IL代码中出现了try…finally语句块、Monitor.Enter与Monotor.Exit方法。然后把代码更改一下再编译看看IL

IL代码

代码比较相似,但并非等价,实际上与lock语句块等价的代码如下

那么既然lock本质上是调用了Monitor,那Monitor是如何通过对一个对象加锁,然后实现线程同步。原来每个在托管堆里面的对象都有两个固定的成员,一个指向该对象类型的指针,另一个是指向一个线程同步块索引。这个索引指向一个同步块数组的元素,Monitor对线程加锁就是靠这个同步块。按照Jeffrey(CLR via C#的作者)的说法同步块中有三个字段,所有权的线程Id,等待线程的数量,递归的次数。然而我通过另一批文章了解到线程同步块的成员并非单纯这几个,有兴趣的同学可以去阅读《揭示同步块索引》的文章,有两篇。 当Monitor需要为某个对象obj加锁时,它会检查obj的同步块索引有否为数组的某个索引,如果是-1的,则从数组中找出一个空闲的同步块与之关联,同时同步块的所有权线程Id就记录下当前线程的Id;当再次有线程调用Monitor的时候就会检查同步块的所有权Id和当前线程Id是否对应上,能对应上的就让其通过,在递归次数上加1,如果对应不上的就把该线程扔到一个就绪队列(这个队列实际上也是存在同步块里面)中,并将其阻塞;这个同步块会在调用Exit的时候检查递归次数确保递归完了就清除所有权线程Id。通过等待线程数量得知是否有线程在等待,如果有则从等待队列中取出线程并释放,否则就解除与同步块的关联,让同步块等待被下个被加锁的对象使用。

Monitor中还有一对方法Wait与Pulse。前者可以使得获得到锁的线程短暂地将锁释放,而当前线程就会被阻塞而放入等待队列中。直到其他线程调用了Pulse方法,才会从等待队列中把线程放到就绪队列中,等待下次锁被释放时,才有机会被再次获取锁,具体能否获取就要看等待队列中的情况了。

ReaderWriterLock读写锁,传统的lock关键字(即等价于Monitor的Enter和Exit),他对共享资源的锁是全互斥锁,一经加锁的资源其他资源完全不能访问。

而ReaderWriterLock对互斥资源的加的锁分读锁与写锁,类似于数据库中提到的共享锁和排他锁。大致情况是加了读锁的资源允许多个线程对其访问,而加了写锁的资源只有一个线程可以对其访问。两种加了不同缩的线程都不能同时访问资源,而严格来说,加了读锁的线程只要在同一个队列中的都能访问资源,而不同队列的则不能访问;加了写锁的资源只能在一个队列中,而写锁队列中只有一个线程能访问资源。区分读锁的线程是否在于统一个队列中的判断标准是,本次加读锁的线程与上次加读锁的线程这个时间段中,有否别的线程加了写锁,没没别的线程加写锁,则这两个线程都在同一个读锁队列中。

ReaderWriterLockSlim和ReaderWriterLock类似,是后者的升级版,出现在.NET Framework3.5,据说是优化了递归和简化了操作。在此递归策略我尚未深究过。目前大概列举一下它们通常用的方法

ReaderWriterLock常用的方法

Acqurie或Release ReaderLock或WriteLock 的排列组合

UpGradeToWriteLock/DownGradeFromWriteLock 用于在读锁中升级到写锁。当然在这个升级的过程中也涉及到线程从读锁队列切换到写锁队列中,因此需要等待。

ReleaseLock/RestoreLock 释放所有锁和恢复锁状态

ReaderWriterLock实现IDispose接口,其方法则是以下模式

TryEnter/Enter/Exit ReadLock/WriteLock/UpGradeableReadLock

(以上内容引用自另一篇笔记《ReaderWriterLock》)

CoutdownEvent比较少用的混合构造,这个跟Semaphore相反,体现在Semaphore是在内部计数(也就是信号量)达到最大值的时候让线程阻塞,而CountdownEvent是在内部计数达到0的时候才让线程阻塞。其方法有

AddCount //计数递增;
Signal //计数递减;
Reset //计数重设为指定或初始;
Wait //当且仅当计数为0才不阻塞,否则就阻塞。

Barrier也是一个比较少用的混合构造,用于处理多线程在分步骤的操作中协作问题。它内部维护着一个计数,该计数代表这次协作的参与者数量,当不同的线程调用SignalAndWait的时候会给这个计数加1并且把调用的线程阻塞,直到计数达到最大值的时候,才会释放所有被阻塞的线程。假设还是不明白的话就看一下MSND上面的示例代码

这里给Barrier初始化的参与者数量是3,同时每完成一个步骤的时候会调用委托,该方法是输出count的值步骤索引。参与者数量后来增加了两个又减少了一个。每个参与者的操作都是相同,给count进行原子自增,自增完则调用SgnalAndWait告知Barrier当前步骤已完成并等待下一个步骤的开始。但是第三次由于回调方法里抛出了一个异常,每个参与者在调用SignalAndWait的时候都会抛出一个异常。通过Parallel开始了一个并行操作。假设并行开的作业数跟Barrier参与者数量不一样就会导致在SignalAndWait会有非预期的情况出现。

接下来说两个Attribute,这个估计不算是同步构造,但是也能在线程同步中发挥作用

MethodImplAttribute这个Attribute适用于方法的,当给定的参数是MethodImplOptions.Synchronized,它会对整个方法的方法体进行加锁,凡是调用这个方法的线程在没有获得锁的时候就会被阻塞,直到拥有锁的线程释放了才将其唤醒。对静态方法而言它就相当于把该类的类型对象给锁了,即lock(typeof(ClassType));对于实例方法他就相当于把该对象的实例给锁了,即lock(this)。最开始对它内部调用了lock这个结论存在猜疑,于是用IL编译了一下,发现方法体的代码没啥异样,查看了一些源码也好无头绪,后来发现它的IL方法头跟普通的方法有区别,多了一个synchronized

于是网上找各种资料,最后发现"junchu25"的博客[1][2]里提到用WinDbg来查看JIT生成的代码。

调用Attribute的

调用lock的

对于用这个Attribute实现的线程同步连Jeffrey都不推荐使用。

System.Runtime.Remoting.Contexts.SynchronizationAttribute这个Attribute适用于类,在类的定义中加了这个Attribute并继承与ContextBoundOject的类,它会对类中的所有方法都加上同一个锁,对比MethodImplAttribute它的范围更广,当一个线程调用此类的任何方法时,如果没有获得锁,那么该线程就会被阻塞。有个说法是它本质上调用了lock,对于这个说法的求证就更不容易,国内的资源少之又少,里面又涉及到AppDomain,线程上下文,最后核心的就是由SynchronizedServerContextSink这个类去实现的。AppDomain应该要另立篇进行介绍。但是在这里也要稍微说一下,以前以为内存中就是有线程栈与堆内存,而这只是很基本的划分,堆内存还会划分成若干个AppDomain,在每个AppDomain中也至少有一个上下文,每个对象都会从属与一个AppDomain里面的一个上下文中。跨AppDomain的对象是不能直接访问的,要么进行按值封送(相当于深复制一个对象到调用的AppDomain),要么就按引用封送。对于按引用封送则需要该类继承MarshalByRefObject。对继承了这个类的对象进行调用时都不是调用类的本身,而是通过代理的形式进行调用。那么跨上下文的也需要进行按值封送操作。平常构造的一个对象都是在进程默认AppDomain下的默认上下文中,而使用了SynchronizationAttribute特性的类它的实例是属于另外的一个上下文中,继承了ContextBoundObject基类的类进行跨上下文访问对象时也是通过按引用封送的方式用代理访问对象,并非访问到对象本身。至于是否跨上下文访问对象可以通过的RemotingServices.IsObjectOutOfContext(obj)方法进行判断。SynchronizedServerContextSink是mscorlib的一个内部类。当线程调用跨上下文的对象时,这个调用会被SynchronizedServerContextSink封装成WorkItem的对象,该对象也mscorlib的中的一个内部类,SynchronizedServerContextSink就请求SynchronizationAttribute,Attribute根据现在是否有多个WorkItem的执行请求来决定当前处理的这个WorkItem会马上执行还是放到一个先进先出的WorkItem队列中按顺序执行,这个队列是SynchronizationAttribute的一个成员,队列成员入队出队时或者Attribute判断是否马上执行WorkItem时都需要获取一个lock的锁,被锁的对象也正是这个WorkItem的队列。这里面涉及到几个类的交互,鄙人现在还没完全看清,以上这个处理过程可能有错,待分析清楚再进行补充。不过通过这个Attribute实现的线程同步按逼人的直觉也是不推荐使用的,主要是性能方面的损耗,锁的范围也比较大。

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持我们!

(0)

相关推荐

  • 详细解析C#多线程同步事件及等待句柄

    最近捣鼓了一下多线程的同步问题,发现其实C#关于多线程同步事件处理还是很灵活,这里主要写一下,自己测试的一些代码,涉及到了AutoResetEvent 和 ManualResetEvent,当然还有也简要提了一下System.Threading.WaitHandle.WaitOne .System.Threading.WaitHandle.WaitAny和System.Threading.WaitHandle.WaitAll ,下面我们一最初学者的角度来看,多线程之间的同步. 假设有这样的一个场

  • C#简单多线程同步和优先权用法实例

    本文实例讲述了C#简单多线程同步和优先权用法.分享给大家供大家参考.具体分析如下: 本文实例题目如下: 麦当劳有两个做汉堡的厨师(工号:11,12)和三个销售人员(工号:21,22,23). 厨师生产汉堡,并负责将做好的汉堡放入货架,货架台大小有限,最多放6个汉堡,11和12不能同时往货架台上放汉堡,11具有优先权. 销售人员负责销售食品,三个销售人员取食品时,货架不能为空,三人不能同时取,23优先权最高,21最低.21卖的最快,取得频率最高,22次之. 一天的工作量是销售70个汉堡. 这里先来

  • C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法

    本文实例讲述了C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法.分享给大家供大家参考,具体如下: 摘要:C#提供了System.Threading.ReaderWriterLock类以适应多用户读/单用户写的场景.该类可实现以下功能:如果资源未被写操作锁定,那么任何线程都可对该资源进行读操作锁定,并且对读操作锁数量没有限制,即多个线程可同时对该资源进行读操作锁定,以读取数据. 使用Monitor或Mutex进行同步控制的问题:由于独占访问模型不允许任何形式的

  • C#多线程及同步示例简析

    60年代,在OS中能拥有资源和独立运行的基本单位是进程,然而随着计算机技术的发展,进程出现了很多弊端,一是由于进程是资源拥有者,创建.撤消与切换存在较大的时空开销,因此需要引入轻型进程:二是由于对称多处理机(SMP)出现,可以满足多个运行单位,而多个进程并行开销过大. 因此在80年代,出现了能独立运行的基本单位--线程(Threads).        线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.一个标准的线程由线程ID,当前指令指针(P

  • C#实现多线程的同步方法实例分析

    本文主要描述在C#中线程同步的方法.线程的基本概念网上资料也很多就不再赘述了.直接接入 主题,在多线程开发的应用中,线程同步是不可避免的.在.Net框架中,实现线程同步主要通过以下的几种方式来实现,在MSDN的线程指南中已经讲了几种,这里结合作者实际中用到的方式一起说明一下. 1. 维护自由锁(InterLocked)实现同步 2. 监视器(Monitor)和互斥锁(lock) 3. 读写锁(ReadWriteLock) 4. 系统内核对象 1) 互斥(Mutex), 信号量(Semaphore

  • c#.net多线程编程教学——线程同步

    随着对多线程学习的深入,你可能觉得需要了解一些有关线程共享资源的问题. .NET framework提供了很多的类和数据类型来控制对共享资源的访问. 考虑一种我们经常遇到的情况:有一些全局变量和共享的类变量,我们需要从不同的线程来更新它们,可以通过使用System.Threading.Interlocked类完成这样的任务,它提供了原子的,非模块化的整数更新操作. 还有你可以使用System.Threading.Monitor类锁定对象的方法的一段代码,使其暂时不能被别的线程访问. System

  • 详解C#多线程之线程同步

    多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程:其二是线程同步问题,鄙人现在学习与探究的是线程同步问题. 通过学习<CLR via C#>里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造.所谓基元则是在代码中使用最简单的构造.基原构造又分成两类,一个是用户模式,另一个是内核模式.而混合构造则是在内部会使用基元构造的用户模

  • 详解python多线程之间的同步(一)

    引言: 线程之间经常需要协同工作,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直到该线程完成对数据的操作.这些技术包括临界区(Critical Section),互斥量(Mutex),信号量(Semaphore),事件Event等. Event threading库中的event对象通过使用内部一个flag标记,通过flag的True或者False的变化来进行操作.      名称                                      含义 set( )

  • 详解Linux多线程使用信号量同步

    信号量.同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已.但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆. 一.什么是信号量 线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作.如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行. 而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍.而信号量一般常用于保护

  • 详解iOS 多线程 锁 互斥 同步

    在iOS中有几种方法来解决多线程访问同一个内存地址的互斥同步问题: 方法一,@synchronized(id anObject),(最简单的方法) 会自动对参数对象加锁,保证临界区内的代码线程安全 @synchronized(self) { // 这段代码对其他 @synchronized(self) 都是互斥的 // self 指向同一个对象 } 方法二,NSLock NSLock对象实现了NSLocking protocol,包含几个方法: lock,加锁 unlock,解锁 tryLock

  • 详解Java编程中线程同步以及定时启动线程的方法

    使用wait()与notify()实现线程间协作 1. wait()与notify()/notifyAll() 调用sleep()和yield()的时候锁并没有被释放,而调用wait()将释放锁.这样另一个任务(线程)可以获得当前对象的锁,从而进入它的synchronized方法中.可以通过notify()/notifyAll(),或者时间到期,从wait()中恢复执行. 只能在同步控制方法或同步块中调用wait().notify()和notifyAll().如果在非同步的方法里调用这些方法,在

  • 详解C#多线程编程之进程与线程

    一. 进程 简单来说,进程是对资源的抽象,是资源的容器,在传统操作系统中,进程是资源分配的基本单位,而且是执行的基本单位,进程支持并发执行,因为每个进程有独立的数据,独立的堆栈空间.一个程序想要并发执行,开多个进程即可. Q1:在单核下,进程之间如何同时执行? 首先要区分两个概念--并发和并行 并发:并发是指在一段微小的时间段中,有多个程序代码段被CPU执行,宏观上表现出来就是多个程序能"同时"执行. 并行:并行是指在一个时间点,有多个程序段代码被CPU执行,它才是真正的同时执行. 所

  • 详解Java中的线程模型与线程调度

    JAVA线程模型 线程的实现主要有3种方式: 使用内核线程实现(1:1) 使用用户线程实现(1:N) 使用用户线程加轻量级进程实现(N:M) 使用内核线程实现(Kernel-Level Thread, KLT)(1:1) 内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程的切换,内核通过操作调度器对线程进行调度,并负责将线程的任务映射到各个处理器上. 程序一般不会直接去使用内核,而是去使用线程的一种高级接口--轻量级进程(Light Weight Process,LWP),轻量级

  • 详解Java多线程与并发

    一.进程与线程 进程:是代码在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位. 线程:是进程的一个执行路径,一个进程中至少有一个线程,进程中的多个线程共享进程的 资源. 虽然系统是把资源分给进程,但是CPU很特殊,是被分配到线程的,所以线程是CPU分配的基本单位. 二者关系: 一个进程中有多个线程,多个线程共享进程的堆和方法区资源,但是每个线程有自己的程序计数器和栈区域. 程序计数器:是一块内存区域,用来记录线程当前要执行的指令地址 . 栈:用于存储该线程的局部变量,这些局部变量是

  • 分析详解python多线程与多进程区别

    目录 1 基础知识 1.1 线程 1.2 进程 1.3 两者的区别 2 Python 多进程 2.1 创建多进程 方法1:直接使用Process 方法2:继承Process来自定义进程类,重写run方法 2.2 多进程通信 Queue Pipe 2.3 进程池 3 Python 多线程 3.1 GIL 3.2 创建多线程 方法1:直接使用threading.Thread() 方法2:继承threading.Thread来自定义线程类,重写run方法 3.3 线程合并 3.4 线程同步与互斥锁 3

  • Java详解实现多线程的四种方式总结

    目录 前言 一.四种方式实现多线程 1.继承Thread类创建线程 2.实现Runnable接口创建线程 3.实现Callable接口 4.实现有返回结果的线程 二.多线程相关知识 1.Runnable 和 Callable 的区别 2.如何启动一个新线程.调用 start 和 run 方法的区别 3.线程相关的基本方法 4.wait()和 sleep()的区别 5.多线程原理 前言 Java多线程实现方式主要有四种: ① 继承Thread类.实现Runnable接口 ② 实现Callable接

随机推荐