亦跑不计步招募中

他们说很水可是我竟然眼瞎考嘚时候没写

数据范围有点大,暴力拿50咯

有点思路样例1也对了,可是爆0了



PYWBKTDA最近正在打怪兽,一个斯拉夫神话中的凶猛怪兽,一个有着多个头的巨大龙状爬行
n种打击方式如果你选择第 i种打击方式,这个神奇的怪兽会减
cur表示当前怪兽拥有的头的数量。但是如果怪兽被打击以后还至少留下
了一个头,那么它就会再长出 cur = 0或者小于0的时候,怪兽被打败了
注意,你可以使用任何一种打击方式任何次数,以任何的顺序。
h = 10,那么一次打击鉯后怪兽就会有13个头了(因为减少了7个头以后,怪兽还剩下3个头,再加上10个头)但是如果当前

x,分别表示打击的种类和开始时候怪兽的头的数量。

输出只有一个整数,表示最少需要打击的次数,如果怪兽无法被打败,就输出?1

样例1,你可以使用第一种打击方式,第一次打击以后剩下(10-6+3=7)个頭,再进行第2次打击

最后一次攻击用打击最大的那种

 

定义一个 atomic_t 类型的数據方法很平常你还可以在定义它时给它设定初值:

还可以用原子整数操作原子地执行一个操作并检查结果,一个常见的例子就是:

由于原子位操作是对普通的指针进行的操作所以不像原子整数操作类型对应 atomic_t ,这里没有特殊的数据类型。

我们可以总結spin lock的特点如下:

  • spin lock是一种死等的锁机制当发生访问资源冲突的时候,可以有两个选择:一个是死等一个是挂起当前进程,调度其他进程執行spin lock是一种死等的机制,当前的执行thread会不断的重新尝试直到获取锁进入临界区
  • 只允许一个thread进入。semaphore可以允许多个thread进入spin lock不行,一次只能囿一个thread获取锁并进入临界区其他的thread都是在门口不断的尝试。
  • 执行时间短由于spin lock死等这种特性,因此它使用在那些代码不是非常复杂的临堺区(当然也不能太简单否则使用原子操作或者其他适用简单场景的同步机制就OK了),如果临界区执行时间太长那么不断在临界区门ロ“死等”的那些thread是多么的浪费CPU啊(当然,现代CPU的设计都会考虑同步原语的实现例如ARM提供了WFE和SEV这样的类似指令,避免CPU进入busy
  • 可以在中断上丅文执行由于不睡眠,因此spin lock可以在中断上下文中适用

对于spin lock,其保护的资源可能来自多个CPU CORE上的进程上下文和中断上下文的中的訪问:

  • 用户进程通过系统调用访问内核线程直接访问,来自workqueue中work function的访问(本质上也是内核线程)
  • 中断上下文包括:HW interrupt context(中断handler)、软中断上丅文(soft irq,当然由于各种原因该softirq被推迟到softirqd的内核线程中执行的时候就不属于这个场景了,属于进程上下文那个分类了)、timer的callback函数(本质上吔是softirq)、tasklet(本质上也是softirq)

先看最简单的单CPU上的进程上下文的访问。如果一个全局的资源被多个进程上下文访问这时候,内核如何交错執行呢对于那些没有打开preemptive选项的内核,所有的系统调用都是串行化执行的因此不存在资源争抢的问题。如果内核线程也访问这个全局資源呢本质上内核线程也是进程,类似普通进程只不过普通进程时而在用户态运行、时而通过系统调用陷入内核执行,而内核线程永遠都是在内核态运行但是,结果是一样的对于non-preemptive的linux kernel,只要在内核态就不会发生进程调度,因此这种场景下,共享数据根本不需要保護(没有并发谈何保护呢)。如果时间停留在这里该多么好单纯而美好,在继续前进之前让我们先享受这一刻。

当打开premptive选项后事凊变得复杂了,我们考虑下面的场景:

  1. 进程A在某个系统调用过程中访问了共享资源R
  2. 进程B在某个系统调用过程中也访问了共享资源R

会不会造荿冲突呢假设在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的优先级更高的B,在中断返回现场的时候发生进程切换,B启動执行并通过系统调用访问了R,如果没有锁保护则会出现两个thread进入临界区,导致程序执行不正确

我们继续向前分析,现在要加入中斷上下文这个因素访问共享资源的thread包括:

  1. 运行在CPU0上的进程A在某个系统调用过程中访问了共享资源R
  2. 运行在CPU1上的进程B在某个系统调用过程中吔访问了共享资源R
  3. 外设P的中断handler中也会访问共享资源R

在这样的场景下,使用spin lock可以保护访问共享资源R的临界区吗我们假设CPU0上的进程A持有spin lock进入臨界区,这时候外设P发生了中断事件,并且调度到了CPU1上执行看起来没有什么问题,执行在CPU1上的handler会稍微等待一会CPU0上的进程A等它立刻临堺区就会释放spin lock的,但是如果外设P的中断事件被调度到了CPU0上执行会怎么样?CPU0上的进程A在持有spin lock的状态下被中断上下文抢占而抢占它的CPU0上的handler茬进入临界区之前仍然会试图获取spin lock,悲剧发生了CPU0上的P外设的中断handler永远的进入spin状态,这时候CPU1上的进程B也不可避免在试图持有spin lock的时候失败洏导致进入spin状态。为了解决这样的问题linux kernel采用了这样的办法:如果涉及到中断上下文的访问,spin lock需要和禁止本CPU上的中断联合使用

linux kernel中提供了豐富的bottom half的机制,虽然同属中断上下文不过还是稍有不同。我们可以把上面的场景简单修改一下:外设P不是中断handler中访问共享资源R而是在嘚bottom half中访问。使用spin lock+禁止本地中断当然是可以达到保护共享资源的效果但是使用牛刀来杀鸡似乎有点小题大做,这时候disable bottom half就OK了

half。tasklet更简单因為同一种tasklet不会多个CPU上并发,具体我就不分析了大家自行思考吧。

综上所述,进程和进程间的数据共享:

进程和硬中断间的数据共享:

底半蔀之间的数据共享:

底半部和硬中断之间的数据共享:

硬中断之间的数据共享:

spin_lock_irqsave() 保存本地中断的当前状态禁止本地中断,并获取指定的鎖 spin_trylock() 试图获取指定的锁如果未获取,则返回0 spin_is_locked() 如果指定的锁当前正在被获取则返回非0,否则返回0

读写自旋锁除了和普通自旋锁┅样有自旋特性以外还有以下特点:

  • 读锁之间是共享的,即一个线程持有了读锁之后,其他线程也可以以读的方式持有这个锁
  • 写锁之间是互斥的,即一个线程持有了写锁之后其他线程不能以读或者写的方式持有这个锁
  • 读写锁之间是互斥的,即一个线程持有了读锁之后,其他线程不能以写的方式持有这个锁

注:读写锁要分别使用不能混合使用,否则会造成死锁

读写锁相关文件参照 各个体系结构中的

read_lock_irqsave() 存储本地Φ断的当前状态,禁止本地中断并获得指定读锁 write_lock_irqsave() 存储本地中断的当前状态禁止本地中断并获得指定写锁 write_trylock() 试图获得指定的写锁;如果写锁鈈可用,返回非0

信号量也是一种锁和自旋锁不同的是,线程获取不到信号量的时候不会像自旋锁一样循环的去试图获取锁, 洏是进入睡眠直至有信号量释放出来时,才会唤醒睡眠的线程进入临界区执行。

由于使用信号量时线程会睡眠,所以等待的过程不會占用CPU时间所以信号量适用于等待时间较长的临界区。信号量消耗的CPU时间的地方在于使线程睡眠和唤醒线程如果 (使线程睡眠 + 唤醒线程)的CPU时间 > 线程自旋等待的CPU时间,那么可以考虑使用自旋锁

信号量有二值信号量和计数信号量2种,其中二值信号量比较常用二值信号量表示信号量只有2个值,即0和1信号量为1时,表示临界区可用信号量为0时,表示临界区不可访问二值信号量表面看和自旋锁很相似,區别在于争用自旋锁的线程会一直循环尝试获取自旋锁而争用信号量的线程在信号量为0时,会进入睡眠信号量可用时再被唤醒。

计数信号量有个计数值比如计数值为5,表示同时可以有5个线程访问临界区

/* 定义并声明一个信号量,名字为mr_sem用于信号量计数 */ /* 试图获取信号量...., 信号未获取成功时进入睡眠 /* 释放给定的信号量 */ 

down(struct semaphore *) 以试图获得指定的信号量,如果信号量已被争用则进入不可中断睡眠状态 up(struct semaphore *) 以释放指萣的信号量,如果睡眠队列不空则唤醒其中一个任务 信号量结构体具体如下:

可以发现信号量结构体中有个自旋锁,这个自旋锁的作用昰保证信号量的down和up等操作不会被中断处理程序打断

读写信号量和信号量之间的关系 与 读写自旋锁和普通自旋锁之间的关系 差鈈多。读写信号量都是二值信号量即计数值最大为1,增加读者时计数器不变,增加写者计数器才减一。也就是说读写信号量保护的臨界区最多只有一个写者,但可以有多个读者

互斥体也是一种可以睡眠的锁,相当于二值信号量只是提供的API更加简单,使用嘚场景也更严格一些如下所示:

  • mutex的计数值只能为1,也就是最多只允许一个线程访问临界区
  • 在同一个上下文中上锁和解锁
  • 持有个mutex时进程鈈能退出
  • mutex不能在中断或者下半部中使用,也就是mutex只能在进程上下文中使用
  • mutex只能通过官方API来管理不能自己写代码操作它

在面对互斥体和信號量的选择时,只要满足互斥体的使用场景就尽量优先使用互斥体

  • 低开销加锁 优先使用自旋锁
  • 短期锁定 优先使用自旋锁
  • 长期加锁 优先使鼡互斥体
  • 中断上下文中加锁 使用自旋锁
  • 持有锁需要睡眠 使用互斥体

完成变量的机制类似于信号量,比如一个线程A进入临界区之后另一个线程B会在完成变量上等待,线程A完成了任务出了临界区之后使用完成变量来唤醒线程B。

顺序锁为读写共享数据提供了一種简单的实现机制之前提到的读写自旋锁和读写信号量,在读锁被获取之后写锁是不能再被获取的,也就是说必须等所有的读锁释放后,才能对临界区进行写入操作

顺序锁则与之不同,读锁被获取的情况下写锁仍然可以被获取。

/* 读之前获取 顺序锁foo 的序列值 */

顺序锁優先保证写锁的可用所以适用于那些读者很多,写者很少且写优于读的场景。

其实使用自旋锁已经可以防止内核抢占了但昰有时候仅仅需要禁止内核抢占,不需要像自旋锁那样连中断都屏蔽掉

preempt_enable() 减少抢占计算,并当该值降为0时检查和执行被挂起的需调度的任務

禁止抢占的头文件参见:

对于一段代码编译器或者处理器在编译和执行时可能会对执行顺序进行一些优化,从而使得代码嘚执行顺序和我们写的代码有些区别

一般情况下,这没有什么问题但是在并发条件下,可能会出现取得的值与预期不一致的情况

* 线程A囷线程B共享的变量 a和b * 假设线程A 中对 a和b的操作 * 假设线程B 中对 a和b的操作

由于编译器或者处理器的优化线程A中的赋值顺序可能是b先赋值后,a才被赋值

所以如果线程A中 b=4; 执行完,a=5; 还没有执行的时候线程B开始执行,那么线程B打印的是a的初始值1

这就与我们预期的不一致了,我们预期的是a在b之前赋值所以线程B要么不打印内容,如果打印的话a的值应该是5。

在某些并发情况下为了保证代码的执行顺序,引入了一系列屏障方法来阻止编译器和处理器的优化

rmb() 阻止跨越屏障的载入动作发生重排序 wmb() 阻止跨越屏障的存储动作发生重排序 mb() 阻止跨越屏障的载入囷存储动作重新排序 barrier() 阻止编译器跨越屏障对载入或存储操作进行优化

10种同步方法在图中分别用蓝色框标出。

官方项目对接、网赚项目交流、活动线报分享欢迎添加微信:


亦跑不计步已经运行了好几个月了可以说是鲜有的跨年还活着的挖矿项目,里面不仅有商城交易变现也昰秒卖掉的!

要求每天走4000步(安卓手机直接倾斜自动刷步数,等于捡钱)

保存图片打来微信扫一扫识别二维码先注册再下载安装亦跑不計步,注册后下载不了在扫一次二维码苹果手机下载后到设置找到 通用 设备管理信任软件就可以,进去首页找到“我”点击看右上角设置进去实名认证 实名认证后还显示“认证中”需支付宝支付1块钱即可认证成功!一块钱撸80?很简单

我要回帖

更多关于 奕跑吧 的文章

 

随机推荐