关于程序并发

关于程序并发是老生常谈的话题了,工作中也经常去碰到,有必要来总结一下,其实并发与之关联的解决办法就是锁,加锁会消耗程序的性能和一些资源这是肯定的,当然如果能利用本身的原子性操作(指令的完整执行,在执行期间并不会被其他线程去中断,也不会存在上下文的切换),实现无锁编程是最好的。

1.防止重复请求

  最近经常发现数据库中有连续数据结构完全相同的数据,可能是用户连续点击,造成数据库的重复操作,我们需要防止这种操作。解决办法有很多,可以加锁,或者利用自身php指令的原子性,将uri,userid,parms(确保没有随机数据),md5校验后存到seession中,先get(),没有说明有效请求,有说明无效请求,看似很合理,但是get(),set()这种存在坑,比如一个用户连续点击多次,多个请求被多个线程执行,此时执行指令顺序是无法预测的,比如说get()无值可以操作,然后就有set进session,在set到session的过程中可能另一个线程也get()到了没有结果,这样也就连续插入两次了,所以我们选择了redis操作,redis都是原子性的操作而且是单线程的。setnx()方法很好,如果有这个键值就返回false,没有就写入成功,原子性操作,完美!

 /**
     * @return array 检查重复提交
     */
    public function checkRepeatSub()
    {
        $uri = app(‘request‘)->getRequestUri();
        $userid = \App\Services\SaleCarCall\Util\Utils::getInstance()->getUserId();
        $params = app(‘request‘)->all();
        if (empty($params)) {
            return true;
        }
        $info = [
            ‘uri‘ => $uri,
            ‘userid‘ => $userid,
            ‘params‘ => $params
        ];
        $key = ‘RepeatSub.‘ . md5(json_encode($info));
        $redis = RedisHelper::getRedisHandle();
        if (!$redis->setnx($key,1)) {
            throw new DuplicateException(‘Duplicate request‘);
        } else {
            $redis->expire($key,60);
            return true;
        }
    }

2.并发申请资源问题

  我们的系统有时候会出现两个客服申请到了同一个资源的问题,这也是典型的并发的问题,原因有好几点,

  2.1 因为采用的数据库的架构是主从分离,读写分离,一主多从的架构,自然而然的就是读落在了从库中,写落在了主库上,从库io线程读取主库的binlog,然后sql线程执行,而且从库的执行是单线程,主库是多线程并发的,所以无论如何主从一定会有延迟,这就会造成A申请到了一条线索,处理完状态应该是已处理,写到主库上面,因为延迟,从库中这条线索还没有改为已处理,那么这条线索就可能二次处理。所以对于及时性很高,状态变化很快的数据,建议还是读写都在主库。

  2.2 我们客服的工作流程典型的是读->改->写的过程,其实是++i的原理是一样的,涉及到两次操作,++i涉及到两次的内存操作,我们是两次数据库的操作,数据库天然的锁机制为我们提供了方便。所以尽量将读写改封装成一个原子性的操作。对于++i的操作,个人也习惯了redis操作,也就是重复提交那一套。对于数据库的那一套个人认为redis操作也是有一些坑。

  比如:我们加锁redis一定是要有过期时间的,如果执行过程中遇到错误或者抛出异常导致最后的delete锁没有执行,那么这个资源将一直被锁住,无法被其他人申请到,但是我们使用过期时间也是有坑的,正常额流程应该是A申请到了一条线索,申请一个redis锁(setnx),返回false,说明锁被占用,重新申请另一条线索,如果得到锁之后,do something,删除锁,看似读写改 在锁的机制下是原子性的操作,但是这个过期时间的设置很容易出错,比如说过期时间是2S,A申请到了id=1的线索,然后执行处理逻辑,但是处理逻辑执行了5S(可能发生),再第3S的时候,其实这个锁已经失效了,B可以申请到id=1的这个线索了,结果B执行花了1S,然后A执行完,更改数据库,B的操作记录就被覆盖了;或者A的执行完成时间在B之后,那么A删除的锁就不是删除自己加的那个锁了,A删除的就是B的锁,那么C就又可能申请到ID=1的这个线索,导致整个执行过程并不是原子性的了,出现混乱。所以最终我们选择了一种可以天然的利用数据库的行锁的机制(基于索引)。

原理是:在INNODB的存储引擎下,基于多版本的控制,读是不加锁的,(在读改写的机制下可能出现死锁),也就是ABC可能都申请到了id=1的资源,然后都要修改为自己名下的已处理,基于innodb的行锁机制,写操作是原子性的,A先修改,然后BC阻塞等待,A修改完,B修改,然后C修改,最后 A B的更新操作被C覆盖,这是典型的更新丢失的情况。所以,我们在设计数据库的时候,updatetime是数据库层面的,当ABc执行更新操作时候,where id=1 and updatetime=读取出来的时间,这样A更新完成后,updatetime被改变,BC不满足他们读取出来的时间和数据库时间一致的条件,更新失败,重新申请下一条的线索。

   $retry = 0;
        while ($retry < 3) {
            $commonclue = SaleClueQueue::onWriteConnection()->select(‘id‘, ‘updated_at‘)->
            where(‘operator‘, 0)>AppointCallVars::NEXT_CALL_TYPE_MOREN)->where(‘is_double_appoint‘, self::DOUBLE_TYPE)->
            orderBy(‘weight‘, ‘asc‘)->first();
            if (!empty($commonclue)) {

                $updateData = [
                    ‘appoint_status‘ => AppointCallVars::APPOINT_STATUS_CALLING,
                    ‘operator‘ => Utils::getInstance()->getUserId(),
                ];
                $flag = SaleClueQueue::where(‘id‘, $commonclue->id)->where(‘updated_at‘, $commonclue->updated_at)->update($updateData);
                if ($flag) {
                    $log = self::$logPress . ‘申请到线索 : 结果 : ‘ . json_encode($nextcall);
                    LogUtils::getInstance()->logInfo($log);
                    break;
                } else {
                    $retry++;
                }
            } else {
                $retry++;
            }
        }
时间: 2024-10-12 14:07:13

关于程序并发的相关文章

关于微信小程序并发数不能超过五个的问题

wx.request 的最大请求数为5个,超过的部分就请求不到了 昨天遇到个问题,首页的请求数一共有9个,但是在有appid开发时竟然一直都没出错,直到我切到没appid的版本的时候才发现了这个问题. 暂时的解决方案时把请求分别放在 onLoad和onReady中分别请求,就可以解决这个问题了. 本来想使用懒加载,这样能减少服务器的压力,同时也能有更好的体验,但是小程序好像没有提供dom操作的方法,实现起来有一定难度

试解释下列名词:程序的顺序执行,程序的并发执行。

一个计算由若干个操作组成,若这些操作必须按照某种先后次序来执行,以保证操作的结果是正确的,则这类计算过程称为程序的顺序执行过程. 所谓的程序的并发执行是指若干个程序同时在系统中运行,这些程序的执行在时间上是重叠的,一个程序的执行尚未结束,另一个程序的执行已经开始. 补充: 顺序程序的特点: 顺序性.封闭性.可再现性. 并发程序的特点: 失去程序的封闭性.程序与计算不再一一对应.程序并发执行时的相互制约关系.

C程序片段并发性测试以及前趋图的自动生成

VX2012平台.C++完成主程序,JAVA实现界面. 所谓程序并发性是指在计算机系统中同时存在有多个程序,宏观上看,这些程序是同时向前推进的.在单CPU环境下,这些并发执行的程序是交替在CPU上运行的.分析程序之间的的可并发性,并利用伯恩斯坦条件来判定各程序之间能否并发执行,同时通过词法语法分析自动生成程序之间的前趋图(DAG图),对我们分析程序很有好处. //by hfut yzk #include"stdafx.h" #include<iostream> #inclu

如何把我的Java程序变成exe文件?

JAVA是一种“跨平台”的语言,拥有“一次编写,处处运行”的特点,让它成为当今IT行业,必不可少的一门编程语言.每一个软件开发完成之后,应该大家都需要打包程序并发送给客户,常见的方式:java程序打成jar包,web程序打成war包 完成之后再通过某种传输方式,传输给客户让其运行.war包 我们这里不做过多阐述,主要说说jar包的问题,jar包必须需要运行在jre环境中,并且需要通过“java -jar 路径/*.jar”的指令才可以完成运行,很多客户并不知道这个命令,这对于他来说确实有些困难,

[转Go-简洁的并发 ]

http://www.yankay.com/go-clear-concurreny/ Posted on 2012-11-28by yankay 多核处理器越来越普及.有没有一种简单的办法,能够让我们写的软件释放多核的威力?是有的.随着Golang, Erlang, Scala等为并发设计的程序语言的兴起,新的并发模式逐渐清晰.正如过程式编程和面向对象一样,一个好的编程模式有一个极其简洁的内核,还有在此之上丰富的外延.可以解决现实世界中各种各样的问题.本文以GO语言为例,解释其中内核.外延. 并

Java并发编程:进程和线程

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

软考-----进程,线程,管程,程序

 一.进程 1.什么是进程 进程是正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说CPU(寄存器),IO,内存,网络资源等.比如说,同样一个程序,同一时刻被两次运行了,那么他们就是两个独立的进程. 进程是程序的一次执行,该进程可以和其他进程并发执行. 2.为什么引入进程 程序并发执行的时候,需要共享系统的资源,从而导致各程序在执行过程中出现相互制约失去了顺序执行的程序的封闭性. 为了提高计算机系统的效率.增强计算机系统内各种硬件的并行操作能力.操作系统要求程序结构必须适

程序、任务、进程和线程的联系与区别

概念: 程序(program)只是一组指令的有序集合. 任务(task)是最抽象的,是一个一般性的术语,指由软件完成的一个活动.一个任务既可以是一个进程,也可以是一个线程.简而言之,它指的是一系列共同达到某一目的的操作.例如,读取数据并将数据放入内存中.这个任务可以作为一个进程来实现,也可以作为一个线程(或作为一个中断任务)来实现. 进程(process)常常被定义为程序的执行.可以把一个进程看成是一个独立的程序,在内存中有其完备的数据空间和代码空间.一个进程所拥有的数据和变量只属于它自己. 进

从一个简单的Java单例示例谈谈并发

一个简单的单例示例 单例模式可能是大家经常接触和使用的一个设计模式,你可能会这么写 public class UnsafeLazyInitiallization { private static UnsafeLazyInitiallization instance; private UnsafeLazyInitiallization() { } public static UnsafeLazyInitiallization getInstance(){ if(instance==null){ /