应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

告诉你关于多线程的一切

  进程内的多个线程共享同一虚拟地址空间,每个线程都是一个独立的执行流,所以,多个线程对应多个执行流,多个执行流会竞争同一资源的情况,资源包括内存数据、打开的文件句柄、套接字等,如果不加以控制和协调,则有可能出现数据不一致,而这种数据不一致可能导致结果错误,甚至程序奔溃,因此,需要努力避免。
  并发编程的错误非常诡谲且难以定位,它总是隐藏在某个的角落,大多数时候,程序运转良好,等代码交付上线后,莫名其妙的出错,就像墨菲定律描述的那样:凡是可能出错,就一定会出错。
  数据不一致源于什么?
  CPU与内存访问性能差距很大,Cache被作为内存的缓存插入到CPU与内存之间,数据会在Cache里缓存内存数据的副本,内存数据与缓存数据不总是一致。
  比如修改变量(写入数据),如果采取写穿透(WriteThrough)的方式,则会在更新缓存中对应的CacheLine的同时把数据写入内存,如果数据不在缓存,则直接写入内存,但每次写操作都写入内存,而内存的访问时延通常高达几十个指令周期,这种写的方式性能太低。而采用写回(WriteBack)的方式,如数据在LocalCache里,则更新缓存后就直接返回,这样就减少访问内存的频率,也就提升了性能,但这样的话,内存数据和Cache里的数据是不一致的。
  现代处理器朝着多CPU多核架构发展,每个核有自己的L1L2Cache,核之间共享L3Cache,然后再通过总线连接内存,内存被所有CPUCore所共享,所以,一个内存数据会被多个CPUCoreCache,不仅内存与Cache中的数据可能不一致,Cache里的多份拷贝也会不一致,Cache一致性协议用于处理这个问题。
  CPU如何使用内存数据?CPU通常不会直接操作内存
  这是因为有些指令对操作数有限制,比如X8664限制mov指令的源和目的操作数不能都是内存地址,所以把一个字节从一个内存地址复制到另一个内存地址,需要两条mov汇编指令,先从源地址move到寄存器,再从寄存器move到目标地址
  即使mov的一个操作数是内存地址,实际上,CPU处理的时候,也会先将内存地址的数据加载到CacheLine,再作用于CacheLine,而非直接修改内存
  多CPU多核系统上,如果Core的localCache没有对应变量的数据,它并不是只有从内存里加载数据到Cache这一条路,而是会通过CPUCore间消息,从别的CPUCore的Cache里拿数据的拷贝,这个核间消息不是通过共享的总线传递,而是基于Interconnect的messagepassing
  当某个Core更新LocalCache里的数据时,它需要通过CPUCore间消息把这个写入操作传播到其他Core的Cache,如果其他Core也Cache了这个数据,要让对应CacheLine失效,这个叫写传播(WritePropagation),总线嗅探通过感知到核间消息来实现写传播
  另外,某个CPU核心里对数据的操作顺序,必须在其他核心看起来顺序是一样的,这个称为事务的串形化(TransactionSerialization),而做到这一点,则需要CPU的缓存更新需要引入‘锁’的概念,多个核心有相同数据的Cache,那么对于数据的更新,只有拿到锁进行,而基于总线嗅探实现的MESI协议就是为了实现事务串行化,如果一个数据在某个Core的CacheLine是独占(Exclusive)状态,则它相当于拿到了自由修改权,如果一个数据被加载到多个Core的Cache,则是Shared的状态,这时候,需要通过向其他核广播请求,Invalidate其他核里的CacheLine才能修改。
  为什么需要多线程同步?
  我们先用2个例子来描述,如果不做线程同步,程序会出现什么问题。
  例子1
  有一个货物售卖程序,变量intitemnum记录某商品的数量,它被初始值为100(代表可售卖数量为100)。售卖函数检查剩余商品数,如果剩余商品数大于等于售卖数量,则扣除商品件数,并返回成功;否则,返回失败。代码如下:
  c
  boolsell(intnum){
  if(itemnumnum){
  itemnumnum;
  returntrue;
  }
  returnfalse;
  }
  单线程下,sell函数被多次调用,程序运转良好,结果符合预期,但在多线程环境下,会出错。
  为了理解上述代码行为,需要先了解一个基本事实:程序变量存放在内存中,对变量做加减,会先将变量加载到通用寄存器,再执行算术运算(更新寄存器中的值),最后把寄存器的新值存入内存位置。
  寄存器里会保存内存变量值的副本,对变量的加减乘除等运算直接作用于副本,而非变量内存位置。不过,如果对变量赋值,则指令会接受一个内存位置作为操作数,指令会直接操作内存位置。
  多核并行
  假设线程t1和线程t2,分别在core1和core2同时执行,t1执行sell(50),t2执行sell(100)。
  2个线程代表2个执行(指令)流,如果2个执行流同时执行到if(itemnumnum)这一行判断语句。
  内存位置保存的itemnum的值会被分别加载到2个核心的寄存器,因为item的值为100,所以加载到寄存器后也都为100,2个线程的条件检查都顺利通过。
  线程t1,执行减法运算itemnumnum,参数num为50,结果为1005050,更新寄存器。
  线程t2,执行减法运算itemnumnum,参数num为100,结果为1001000,更新寄存器。
  然后,t1和t2线程先后把各自寄存器里的新值store到内存,后一个线程的store操作会覆盖前值。
  如果t1线程先store,内存中的itemnum被修改为50,t2线程再执行store,内存中的itemnum被覆盖,itemnum值被替换为0。
  如果t2线程先store,t1线程后store,则itemnum的最终值为50。
  无论哪种情况,结果都是错误的,我们只有100件商品,却超卖出150件。
  单核并发
  如果程序在单CPU单Core的机器上运行,t1线程和t2线程并发(非并行)交错执行,因为只有一个CPU,所以同一时刻,只有一个线程在执行。
  假设t1在CPU上执行,它把itemnum(100)load到寄存器,判断通过(100100)。
  这时候,发生线程调度(比如t1的时间配额耗尽),t2被调度到CPU上执行,然后t2依次完成load、check、compute和store操作。
  然后t1又被调度到CPU上恢复执行,t1会直接用寄存器中的itemnum副本(100),执行计算,item50的结果为50,更新寄存器,所以t1线程执行后,新值50被store入itemnum所在内存。
  我们期望t1或者t2的sell只有一个操作成功,但结果并非如此。
  CPU核相当于工人,而程序线程相当于任务,核的数量决定了并行度,多个核代表多个任务可以被同时执行,但多线程竞争导致数据的不一致,在单核环境也有可能出现。
  让我们再看一个计数的例子:
  例子2
  全局变量intcount用来计数,我们启动100个线程,每个线程的处理逻辑:在100次循环里累加count;主函数启动线程并等待所有线程执行完成,程序退出前打印count数值,代码如下:
  includethreadincludeiostreamintcount0;voidthreadproc(){for(inti0;i100;i){count;}}intmain(){std::threadthreads〔100〕;for(autox:threads){xstd::move(std::thread(threadproc));}for(autox:threads){x。join();}std::coutcount:countstd::endl;return0;}
  我的机器上打印count:9742,而我们期望的结果是10010010000,结果与预期不符,为什么会这样?
  简单的一行count,实际上也包括:从内存加载变量的值到寄存器(load),寄存器中更新值(update),将寄存器中的新值存入内存(store)。
  因为多个线程并发并行执行,而loadupdatestore的三步不是原子的,不能在一个cpucycle内完成,所以多个线程可能加载了相同的旧值,在寄存器中分别更新,再写入变量count的内存位置,导致最终值比期望的1000小,而且运行多次,会出现不同的结果。
  问题出在哪里?
  上述2个例子都表明:不加控制的多线程并发并行访问同一数据,会导致数据不一致,从而产生错误的结果,所以,需要某种同步机制,协调多线程的运行,确保结果的正确性。
  上述问题的根因都出在多线程对数据的竞争访问,数据竞争不仅因为对数据的访问不能在一个指令周期内完成,也因为我们经常要先读一个变量值,再基于它的值做决策,而这2个步骤的组合并非原子的。
  只有同时满足以下情况,才需要做多线程同步:
  数据竞争是前提条件,多线程对同一数据的并发访问所有线程对数据只读的话,没有问题,不需要同步,必须有线程对数据进行写(修改),多线程混合读写才需要同步对数据的访问在时间上要同时或者交错(多线程对数据读写在开始结束时间上有重叠),如果时间上能错开,也没有问题需要出现数据不一致的情况,是不能忍受的错误(比如的超卖,计数错误),否则,也不必同步(比如只是记一条粗略统计的日志,计数不准确也没有关系),这一点往往被忽略
  保护的到底是什么?
  多线程同步,保护的是数据,而非代码,代码保存在进程的文本段,程序运行过程中,只会从保存文本段的内存位置读取指令序列,而不会修改文本(代码)段。
  细化一下,我们保护的是全局资源数据、static局部数据、或者通过指针引用指向的堆内存上的数据;而普通局部变量则通常不需要保护,因为局部变量位于栈上,每个线程有独立的栈,线程栈是相互隔离的,不通过特殊手段不可互访。
  多线程同步机制有哪些?
  线程间同步的机制有很多,Posix线程同步机制包括互斥锁(Mutex)、读写锁(ReaderWriterLock)、自旋锁(SpinLock)、条件变量(ConditionVariable)、屏障(Barrier)等。
  CC、Java等编程语言都有类似的线程同步机制和编程接口,细节上不尽相同,但概念和原理上都是相通的,我们主要考查Posix线程同步机制。
  互斥锁
  互斥锁,某个线程获得锁之后,直到该线程释放锁之前,其他的线程便没法获得锁,表现出排他的特征。
  互斥锁,本质上是一个内存变量,无他。互斥锁目的是做串行化,即在同一时间点,访问受保护数据的临界代码,只会有一个执行流正在运行,直到这段临界代码执行完,其他执行流才有机会进入这段临界代码,这就从根本上杜绝了某个数据被多个执行流交错访问。
  互斥锁的用法,通常是在访问共享资源前加锁,完成资源访问后再解锁,当申请加锁的时候,锁已经被其他线程持有,那么申请加锁的线程便会阻塞住,当锁被释放(解锁),则阻塞在该锁上的所有线程,都会被唤醒,只有一个线程会加锁成功,其他线程意识到锁处于加锁状态,则又转入阻塞等待解锁,因此,一次只有一个线程会前进。用mutex对sell做同步的代码如下:
  c
  boolsell(intnum){
  mutex。lock();
  if(itemnumnum){
  itemnumnum;
  mutex。unlock();
  returntrue;
  }
  mutex。unlock();
  returnfalse;
  }
  加锁保护后,对itemnum的判断和减值这2个行代码,将变成密不可分的原子操作,多线程同时调用sell()都不会出现前述的超卖错误。
  RAII
  上面的程序,有一个容易出错的点,那就是在两次返回的时候都需要解锁,如果函数有多个返回点,则需要在每个返回点都小心的解锁,如果逻辑更复杂一点,或者调用的其他函数抛出异常,则很难确保unlock得到正确的调用。
  所以,C提供一个叫RAII的技术,常用来应对这种情况,RAII利用C临时对象的析构在对象出作用域时一定会得到调用的特性,来提升安全性,通过构建一个临时对象,在对象的构造函数里加锁,在析构函数里解锁,来完成配对的操作,常用于加解锁,打开关闭文件句柄,申请释放资源等操作。
  classLockGuard{std::mutexmutex;LockGuard(constLockGuard)delete;LockGuard(LockGuard)delete;LockGuardoperator(constLockGuard)delete;LockGuardoperator(LockGuard)delete;public:LockGuard(std::mutexmutex):mutex(mutex){mutex。lock();}LockGuard(){mutex。unlock();}};intitemnum100;std::mutexitemnummutex;boolsell(intnum){LockGuardlg(itemnummutex);if(itemnumnum){itemnumnum;returntrue;}returnfalse;}
  注意
  通过互斥锁对数据进行保护,需要开发者遵从约束,按某种规定的方式编写数据访问代码,这种约束是对程序员的隐式约束,它并非某种强制的限制。
  比如使用互斥量对数据x进行并发访问控制,假设有2处代码对该数据竞争访问,其中一处用互斥量做了保护,而另外一处,没有使用互斥锁加以保护,则代码依然能通过编译,程序依然能运行,只是结果上可能是错误,这个跟现实中没有钥匙开锁就不能得到(访问)权限是不一样的。
  原子操作
  原子操作,从语义上理解,既原子性的执行一系列操作,这一系列操作是密不可分的整体,要么都做,要么都不做,中间也不会被穿插进其他操作。
  比如对一个整型变量自增(count),对长度不大于机器字长,且满足数据类型自身对齐要求的变量的Load和Store操作,仅需要一条指令即可完成,都是原子操作(一条指令完成是原子操作的必要非充分条件),其他core无法观察到中间状态,但因为count需要执行LoadUpdateStore三步骤,多核环境下,这三步是可以相互穿插的,不是原子操作。
  C提供一种类型为std::atomic的类模板,它提供fetchsubfetchadd等原子操作。
  原子操作是编写Lockfree多线程程序的基础,原子操作只保证原子性,不保证操作顺序。在Lockfree多线程程序中,光有原子操作是不够的,需要将原子操作和MemoryBarrier结合起来,才能实现免锁。
  原子操作常用于与顺序无关的场景,比如前面例子中的计数,用原子变量改写后,则会输出符合预期的count10000。
  c
  std::atomiccount0;
  voidthreadproc(){
  for(inti0;i100;i){
  count;原子的自增
  }
  }
  程序顺序
  对单线程程序而言,代码会一行行顺序执行,比如先后对a1;b2;赋值,则执行完a1;才会执行b2;,从程序角度看到的代码行一次执行,我们在此基础上构建软件,以此作为讨论的基础。
  内存序
  内存序是指从某个角度观察到的对于内存的读和写所真正发生的顺序。
  内存操作顺序并不唯一:在一个包含core0和core1的CPU中,core0和core1有着各自的内存操作顺序,这两个内存操作顺序不一定相同;从包含多个Core的CPU的视角看到的全局内存操作顺序跟单core视角看到的内存操作顺序亦不同,而这种不同,对于有些程序逻辑而言,是不可接受的,所以,需要有同步机制确保内存序的一致。
  c
  a1;
  b2;
  程序序要求a1在b2之前执行,但内存操作顺序可能并非如此,对a赋值1并不确保发生在对b赋值2之前,如果编译器认为对b赋值没有依赖对a赋值,那它完全可以在编译期为了性能调整编译后的汇编指令顺序,即使编译器不做调整,到了执行期,也有可能对b的赋值先于对a赋值执行。
  乱序执行
  乱序执行会引起内存顺序跟程序顺序不同,乱序执行的原因是多方面的,比如编译器指令重排、超标量指令流水线、预测执行、CacheMiss等。内存操作顺序无非精确匹配程序顺序,这有可能带来混乱,既然有副作用,那为什么还需要乱序执行呢?答案是为了性能。
  我们先看看没有乱序执行之前,早期的有序处理器(InorderProcessors)是怎么处理指令的?
  指令获取,从代码节内存区域取指令到ICache
  如果指令操作数可用,例如位于寄存器中,则分发指令到对应功能模块中;如果操作数不可用,通常是需要从内存加载,则处理器会stall,一直等到它们就绪,加载到cache或拷贝寄存器
  指令被功能单元执行
  功能单元将结果写回寄存器
  乱序处理器(OutoforderProcessors)又是怎么处理指令的呢?
  指令获取,相同
  分发指令到指令队列
  指令在指令队列中等待,一旦操作数就绪,指令就离开指令队列,那怕它之前的指令未被执行
  指令被派往功能单元并被执行
  执行结果放入队列(StoreBuffer),而不是直接写入Cache
  只有更早请求执行的指令结果写入cache后,指令执行结果才写入cache,通过对指令结果排序写入cache,使得执行看起来是有序的。
  指令乱序执行主要由两种因素导致:
  编译期:指令重排(编译器),编译器会为了性能而对指令重排,源码上先后的两行,被编译器编译后,可能调换指令顺序,但编译器会基于一套规则做指令重排,有明显依赖的指令不会被随意重排,指令重排不能破坏程序逻辑。
  运行期:乱序执行(CPU),CPU的超标量流水线、以及预测执行、CacheMiss等都有可能导致指令乱序执行,也就是说,后面的指令有可能先于前面的指令执行。
  StoreBuffer
  InvalidateQueue
  内存屏障
  lockfree
  未完待续。。。

中国足协哭穷无钱请外教?媒体有越南穷?再想起年初一31惨败17亿调节费不翼而飞?中国足协实属巧妇难为无米之炊?中国足协不再相信持中国执照的主帅,毕竟,李铁的贪污腐败案一直在检控机关那里不断的侦破之中,但是,李铁的嘴好像是被封起来,一点……开学季到来!学生党入手小米13是真香,硬件软件都没得挑相信一个新年过去,各位学生朋友已经不再囊中羞涩,个个都是荷包鼓鼓的吧?现在马上就要开学了,有没有想法换部新手机去学校?如果有这个想法,我就給大家省时间了,给大家推荐小米13系列……从中国到英国国际专线服务英国专线主要是通过海运和空运及铁运这三种渠道进行头程运输,其中使用比较多的是空运及铁运,因为铁运是以中欧班列为依托,而空运的时效比较快。尾程大多都是通过当地物流或专线公司的当地……青岛西安郑州,谁是北方第三城?原创刘博团队目前,24座万亿城市成绩单全部出炉。我们对北方TOP10城市的经济数据进行梳理,看看北京天津之后,北方第三城,谁与争锋?01hr我们抽取了GDP、……土耳奇首都安卡拉安卡拉是土耳其政治、经济、文化、交通和贸易的中心,安卡拉为仅次于伊斯坦布尔的全国第二大工业中心,有东西行的铁路干线通全国主要城市和港口,另有公路多条通向各方。航空港保持国内外的……每天坚持喝2两白酒,对身体有没有影响?行家的解答来了笔者认识一个老大爷姓张,身体一直还算不错了,但是前一段时间住进了医院。张大爷平时没有什么特别的爱好,就是喜欢喝点酒,他自己秉承:每天喝二两酒,我能活到99的理念,所以之一也只是……果断把联通10016设为黑名单昨天中午,10016来电问我是不是机主本人,告知我说可以线上和线下兑换积分,她还说会用短信通知我,我接听后表示感谢,然到了晚上我才注意到10010icon的短信内容是办理了什么……QJMOTOR以冠名品牌,参加2023年度Moto2组别赛事2023年1月21日。法恩扎,MOTO2级别2023年团队展示。阵容已经揭晓,包含了去年在意大利队首次亮相中级赛的捷克车手菲利普萨拉奇(FilipSala);Moto2车队的一……春节降价幅度超千元的机型汇总,以下五款重点关注春节降价幅度超千元的机型汇总,以下五款重点关注第一款:realmeGT2Pro这款手机在双十一预计跳水1400左右。采用了一块6。7英寸AMOLED屏幕,支持2K分……皮蓬前妻拉塞尔和乔丹儿子热恋!NBA的圈子小到大家都是亲戚!最近媒体曝光了皮蓬前妻拉塞尔和乔丹二儿子马库斯热吻的照片。拉塞尔48岁,马库斯31岁,妥妥的姐弟恋。乔丹与皮蓬近些年的关系并不好,特别是乔丹纪录片播出后,皮蓬相当不满,他……事关你的收入!国家税务总局最新公告!国家税务总局6日发布公告,2022年度个人所得税综合所得汇算清缴办理时间为2023年3月1日至6月30日。2022年度终了后,居民个人需要汇总2022年1月1日至12月31日取……童书湃妈妈们亲测,这些书可以加进你的购物车又到了亲子学堂荐书时间了,和孩子一起读好书,享受温馨的共读时刻,是一件很美好的事。什么样的书孩子会喜欢?陪孩子一起阅读的妈妈最清楚。哪些书值得加入购物车,听听这些妈妈们的……
2000亿美元蒸发无踪,马斯克陷入危机时刻Pine萧箫发自凹非寺量子位公众号QbitAI贫穷亿万富翁马斯克,日子比以往更不好过了。自今年1月购买股份、4月提出收购、10月正式收购以来,他的身价随着这出……年轻人城会玩,德国门神和超模女友度假,高难度接吻动作庆祝圣诞卡塔尔世界杯早已结束,各支参赛球队可谓是几家欢喜几家愁。对于梅西、迪玛利亚等阿根廷人来说,刚刚过去的世界杯是狂欢的季节。而对于德国人来说,那无疑是悲伤的季节,至于特……阅读非暴力沟通亲子篇的启发陈孝花老师阅读完《非暴力沟通亲子篇》这本书后,对自我尊重有了更深度的思考,陈孝花老师还期待将对书中提到的教学工具在课堂、和团队中实践。让我们一起来看看陈孝花老师的阅读收获吧!……富士康正在加速撤离,120万员工该何去何从?中国的制造业世界闻名,也成就了很多生产制造型企业,富士康就是其中颇有名气的一家。因为和美国苹果公司成为了合作伙伴,富士康的名声也一跃而起。可如今富士康的野心似乎更大了,不再满足……扩大国际互认合作助力中国品牌走出去央视网消息:推进高水平的对外开放需要开放的平台,同时还需要便捷高效的通道。作为内陆城市,南昌是如何探索跨境新通道,助力中国品牌走出去的?正在装运的是江西本土制造的汽车,这……情感漫画女人怀孕的6720小时,没有人能笑着看完写在最后孕周是从末次月经的开始日算起的,整个孕期统共40周,280天,6720个小时。生孩子绝对不是女人一个人的事情,而是一个家庭的事情。妻子怀孕之后,丈夫要……我是退役女足进过工厂端过盘子,现在教孩子踢球月薪过万教练,球队没了?为什么就突然没了啊!直到现在,小谢还记得那群孩子哭得稀里哗啦的样子。作为前女足队员,小谢退役后做过服务员,做过厂妹,在足球产业火爆期,她去了一家中超俱乐部……澳大利亚认识到中国消费者是不可替代的,一个中国游客消费超过两(观察者网讯)澳大利亚认识到中国消费者是不可替代的。彭博社21日以此为题报道称,在中澳贸易关系陷入争端之后,许多澳大利亚出口商面临着巨大的痛苦。报道指出,尽管澳大利亚花费……园林式乡村看苏州昆山巴城镇武神潭村如何打造项目位于苏州昆山市巴城镇武神潭村。又称南武城遗址。遗址东周围有东江、阳澄东湖、斜路港和七浦塘等水域。武神潭村状似八卦图,村庄依托于武神潭现存历史遗址,打造武城文化,营造武……适合自己,才是该追求的生活来源:新华社人生没有标准答案。人生一世,本该各自精彩,有的人却总希望活成别人的模样。不管追逐的是不是自身的真正需求,也不管这样的方式是否适合自己。因为相……天天在用水,你真的用对了吗?水的特性:水是地球上最常见的物质之一,地球表面约有71被水覆盖。它是包括无机化合、人类在内所有生命生存的重要资源,也是生物体最重要的组成部分。它在空气中含量虽少,但却是空……为什么互联网公司到最后,都成了放贷专业户?这个不是声讨,也非论过。相比几年前的不规范,714高炮,断头贷等,目前数家。甚至数十家的所谓高科技或互联网公司旗下的金融(放贷)业务还是好上很多。大致理一下,参与进……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网