目录
发现问题
测试
如何解决
首页 php教程 php手册 解决进程间共享内存,由于某个进程异常退出导致死锁问题

解决进程间共享内存,由于某个进程异常退出导致死锁问题

Jun 06, 2016 pm 08:10 PM
共享 内存 导致 异常 死锁 解决 进程 退出

发现问题 继这篇Blog 解决Nginx和Fpm-Php等内部多进程之间共享数据问题 发完后,进程间共享内存又遇到了新的问题 昨天晚上QP同学上线后,早上看超时报表发现有一台前端机器访问QP超时,比其他前端机器高出了几个数量级,前端的机器都是同构的 难道是这台机器

发现问题

继这篇Blog 解决Nginx和Fpm-Php等内部多进程之间共享数据问题 发完后,进程间共享内存又遇到了新的问题

昨天晚上QP同学上线后,早上看超时报表发现有一台前端机器访问QP超时,比其他前端机器高出了几个数量级,前端的机器都是同构的

难道是这台机器系统不正常?查看系统状态也没有任何异常,统计了一下超时日志,发现超时都发生在早上QP服务重启的过程中,正常情况下服务重启时,ClusterMap 会保证流量的正常分配

难道是ClusterMap有问题?去ClusterMap Server端看了一下,一切正常

难道是订阅者客户端有问题吗?随便找了一台正常的机器和有问题的这台机器对比,查看下日志也没有发现问题,使用查询工具检查这两台机器订阅者代理写的共享内存,发现工具读取共享内存返回的结果不一致,这就更奇怪了,都是相同的订阅者,一台机器有问题一台没问题

难道Server端给他们的消息不一致?去Server端把订阅者的机器列表都打了出来,发现了有问题的机器根本不在订阅者列表里面,说明这台机器没有订阅,貌似有点线索了,我下线了一台它订阅的QP机器验证,发现共享内部数据没有更新,pstack一下这个进程,发现内部的更新线程一直在等锁,导致共享内存数据一直无法更新,gdb跟进去之后,_lock.data.nr_readers一直为1,说明一直有一个读进程占着锁导致写进程无法进入,遍历了所有fpm-php的读进程发现都没有占着锁,这说明在读进程在获得锁后没来得及释放就挂掉了

测试

现在问题已经确认就是获得读锁后进程异常退出导致的,我写个测试程序复现这个问题


(! 2293)-> cat test/read_shared.cpp

#include
SharedUpdateData*   _sharedUpdateData = NULL;
cm_sub::CMMapFile*  _mmapFile = NULL;
int32_t initSharedMemRead(const std::string& mmap_file_path)
{
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if (_mmapFile == NULL || !_mmapFile->open(mmap_file_path.c_str(), FILE_OPEN_WRITE) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData*)_mmapFile->offset2Addr(0);
    return 0;
}
int main(int argc, char** argv)
{
    if (initSharedMemRead(argv[1]) != 0) return -1;
    int cnt = 100;
    while (cnt > 0)
    {
        pthread_rwlock_rdlock( &(_sharedUpdateData->_lock));
        fprintf(stdout, "version = %ld, readers = %u\n",
            _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__nr_readers);
        if (cnt == 190)
        {  
            exit(0);
        }  
        sleep(1);
        pthread_rwlock_unlock( &(_sharedUpdateData->_lock));
        -- cnt;
        usleep(100*1000);
    }
    delete _mmapFile;
}
登录后复制

(! 2293)-> cat test/write_shared.cpp

#include
SharedUpdateData*   _sharedUpdateData = NULL;
cm_sub::CMMapFile*  _mmapFile = NULL;
int32_t initSharedMemWrite(const char* mmap_file_path)
{
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if ( _mmapFile == NULL || !_mmapFile->open(mmap_file_path, FILE_OPEN_WRITE, 1024) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData *)_mmapFile->offset2Addr(0);
    madvise(_sharedUpdateData, 1024, MADV_SEQUENTIAL);
    pthread_rwlockattr_t attr;
    memset(&attr, 0x0, sizeof(pthread_rwlockattr_t));
    if (pthread_rwlockattr_init(&attr) != 0 || pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) != 0)
    {
        return -1;
    }
    pthread_rwlock_init( &(_sharedUpdateData->_lock), &attr);
    _sharedUpdateData->_updateTime = autil::TimeUtility::currentTime();
    _sharedUpdateData->_version = 0; 
    return 0;
}
int main()
{
    if (initSharedMemWrite("data.mmap") != 0) return -1;
    int cnt = 200;
    while (cnt > 0)
    {
        pthread_rwlock_wrlock( &(_sharedUpdateData->_lock));
        ++ _sharedUpdateData->_version;
        fprintf(stdout, "version = %ld, readers = %u\n",
                _sharedUpdateData->_version, _sharedUpdateData->_lock.__data.__nr_readers);
        sleep(1);
        pthread_rwlock_unlock( &(_sharedUpdateData->_lock));
        -- cnt;
        usleep(100*1000);
    }
    delete _mmapFile;
}
登录后复制

无论是读进程还是写进程,获取锁后来不及释放就挂掉都会有这样的问题

如何解决

问题已经复现,想想如何用一个好的办法解决,在网上找了一遍,针对读写锁没有什么好的解决办法,只能在逻辑上自己解决,能想到的是使用超时机制,即写进程内部增加一个超时时间,如果写进程到了这个时间还是不能获得锁,就认为死锁,将读进程的计数减1,这是一个暴力的解决办法,不解释了,如果谁有好的解决办法指导我下

看下读写锁的代码,读写锁和互斥锁相比,更适合用在读多写少的场景,如果读进程需要锁住时间久,就更合适使用读写锁了,我的应该场景是,读多写少,读写时间都非常短;暂时认为互斥锁和读写锁性能差别应该不大,其实读写锁内部同样使用了互斥锁,只不过是锁的时间比较短,锁住互斥区,进去看下是否有人正在写,然后就释放了,
需要注意的是,读写锁默认是写优先的,也就是说当正在写,或者进入写队列准备写时,读锁都是加不上的,需要等待

好,那我们看看互斥锁能否解决我们的问题,互斥锁内部有一个属性叫Robust锁

设置锁为Robust锁: pthread_mutexattr_setrobust_np

        The robustness attribute defines the behavior when the owner
    of  a  mutex  dies.  The value of robustness could be either
    PTHREAD_MUTEX_ROBUST_NP or  PTHREAD_MUTEX_STALLED_NP,  which
    are  defined by the header . The default value of
    the robustness attribute is PTHREAD_MUTEX_STALLED_NP.
        When the owner of a mutex with the  PTHREAD_MUTEX_STALLED_NP
    robustness    attribute    dies,   all   future   calls   to
    pthread_mutex_lock(3C) for this mutex will be  blocked  from
    progress in an unspecified manner.
登录后复制

修复非一致的Robust锁: pthread_mutex_consistent_np

        A consistent mutex becomes inconsistent and is  unlocked  if
    its  owner dies while holding it, or if the process contain-
    ing the owner of the mutex unmaps the memory containing  the
    mutex or performs one of the exec(2) functions. A subsequent
    owner  of  the   mutex   will   acquire   the   mutex   with
    pthread_mutex_lock(3C),  which  will  return  EOWNERDEAD  to
    indicate that the acquired mutex is inconsistent.
        The pthread_mutex_consistent_np() function should be  called
    while  holding  the  mutex  acquired  by  a previous call to
    pthread_mutex_lock() that returned EOWNERDEAD.
        Since the critical section protected by the mutex could have
    been  left  in  an inconsistent state by the dead owner, the
    caller should make the mutex consistent only if it  is  able
    to  make  the  critical  section protected by the mutex con-
    sistent.
登录后复制

简单来说就是当发现EOWNERDEAD时,pthread_mutex_consistent_np函数内部会判断这个互斥锁是不是Robust锁,如果是,并且他OwnerDie了,那么他会把锁的owner设置成自己的进程ID,这样这个锁又可以恢复可用,很简单吧

锁释放是可以解决了,但是通过共享内存在进程间共享数据时,还有一点是需要注意的,就是数据的正确性,即完整性,进程共享不同与线程,如果是一个进程中的多个线程,那么进程异常退出了,其他线程也同时退出了,进程间共享都是独立的,如果一个写线程在写共享数据的过程中,异常退出,导致写入的数据不完整,读进程读取时就会有读到不完整数据的问题,其实数据完整性非常好解决,只需要在共享内存中加一个完成标记就好了,锁住共享区后,写数据,写好之后标记为完成,就可以了,读进程在读取时判断一下完成标记

测试代码见:


(! 2295)-> cat test/read_shared_mutex.cpp

 #include 
 SharedUpdateData*   _sharedUpdateData = NULL;
 cm_sub::CMMapFile*  _mmapFile = NULL;
 int32_t initSharedMemRead(const std::string& mmap_file_path)
 {
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if (_mmapFile == NULL || !_mmapFile->open(mmap_file_path.c_str(), FILE_OPEN_WRITE) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData*)_mmapFile->offset2Addr(0);
    return 0;
 }
 int main(int argc, char** argv)
 {
     if (argc != 2) return -1;
     if (initSharedMemRead(argv[1]) != 0) return -1;   
     int cnt = 10000;
     int ret = 0;
     while (cnt > 0)
     {
         ret = pthread_mutex_lock( &(_sharedUpdateData->_lock));
         if (ret == EOWNERDEAD)
         {
             fprintf(stdout, "%s: version = %ld, lock = %d, %u, %d\n",
                strerror(ret),
                _sharedUpdateData->_version, 
                _sharedUpdateData->_lock.__data.__lock,
                _sharedUpdateData->_lock.__data.__count,
                _sharedUpdateData->_lock.__data.__owner);
             ret = pthread_mutex_consistent_np( &(_sharedUpdateData->_lock));
             if (ret != 0)
             {
                 fprintf(stderr, "%s\n", strerror(ret));
                 pthread_mutex_unlock( &(_sharedUpdateData->_lock));
                 continue;
             }
         }
         fprintf(stdout, "version = %ld, lock = %d, %u, %d\n", 
            _sharedUpdateData->_version, 
            _sharedUpdateData->_lock.__data.__lock,
            _sharedUpdateData->_lock.__data.__count,
            _sharedUpdateData->_lock.__data.__owner);
         sleep(5);
         pthread_mutex_unlock( &(_sharedUpdateData->_lock));
         usleep(500*1000);
         -- cnt;
    }
    fprintf(stdout, "go on\n");
    delete _mmapFile;
 }
登录后复制

(! 2295)-> cat test/write_shared_mutex.cpp

#include 
SharedUpdateData*   _sharedUpdateData = NULL;
cm_sub::CMMapFile*  _mmapFile = NULL;
int32_t initSharedMemWrite(const char* mmap_file_path)
{
    _mmapFile = new (std::nothrow) cm_sub::CMMapFile();
    if ( _mmapFile == NULL || !_mmapFile->open(mmap_file_path, FILE_OPEN_WRITE, 1024) )
    {
        return -1;
    }
    _sharedUpdateData = (SharedUpdateData *)_mmapFile->offset2Addr(0);
    madvise(_sharedUpdateData, 1024, MADV_SEQUENTIAL);
    pthread_mutexattr_t attr;
    memset(&attr, 0x0, sizeof(pthread_mutexattr_t));
    if (pthread_mutexattr_init(&attr) != 0 || pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED) != 0)
    {
        return -1;
    }
    if (pthread_mutexattr_setrobust_np(&attr, PTHREAD_MUTEX_ROBUST_NP) != 0)
    {
        return -1;
    }
    pthread_mutex_init( &(_sharedUpdateData->_lock), &attr);
    _sharedUpdateData->_version = 0;
    return 0;
}
int main()
{
    if (initSharedMemWrite("data.mmap") != 0) return -1;
    int cnt = 200;
    int ret = 0;
    while (cnt > 0)
    {
        ret = pthread_mutex_lock( &(_sharedUpdateData->_lock));
        if (ret == EOWNERDEAD)
        {
            fprintf(stdout, "%s: version = %ld, lock = %d, %u, %d\n",
                    strerror(ret),
                    _sharedUpdateData->_version,
                    _sharedUpdateData->_lock.__data.__lock,
                    _sharedUpdateData->_lock.__data.__count,
                                            _sharedUpdateData->_lock.__data.__owner);
            ret = pthread_mutex_consistent_np( &(_sharedUpdateData->_lock));
            if (ret != 0)
            {
                fprintf(stderr, "%s\n", strerror(ret));
                pthread_mutex_unlock( &(_sharedUpdateData->_lock));
                continue;
            }
        }
        ++ _sharedUpdateData->_version;
        fprintf(stdout, "version = %ld, lock = %d, %u, %d\n", _sharedUpdateData->_version,
                _sharedUpdateData->_lock.__data.__lock,
                _sharedUpdateData->_lock.__data.__count,
                _sharedUpdateData->_lock.__data.__owner);
        usleep(1000*1000);
        pthread_mutex_unlock( &(_sharedUpdateData->_lock));
        -- cnt;
        usleep(500*1000);
    }
    delete _mmapFile;
}
登录后复制

BTW:我们都知道加锁是有开销的,不仅仅是互斥导致的等待开销,还有加锁过程都是有系统调用到内核态的,这个过程开销也很大,有一种互斥锁叫Futex锁(Fast User Mutex),Linux从2.5.7版本开始支持Futex,快速的用户层面的互斥锁,Fetux锁有更好的性能,是用户态和内核态混合使用的同步机制,如果没有锁竞争的时候,在用户态就可以判断返回,不需要系统调用,

当然任何锁都是有开销的,能不用尽量不用,使用双Buffer,释放链表,引用计数,都可以在一定程度上替代锁的使用

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
2 周前 By 尊渡假赌尊渡假赌尊渡假赌
仓库:如何复兴队友
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒险:如何获得巨型种子
1 个月前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

大内存优化,电脑升级16g/32g内存速度没什么变化怎么办? 大内存优化,电脑升级16g/32g内存速度没什么变化怎么办? Jun 18, 2024 pm 06:51 PM

对于机械硬盘、或SATA固态硬盘,软件运行速度的提升会有感觉,如果是NVME硬盘,可能感觉不到。一,注册表导入桌面新建一个文本文档,复制粘贴如下内容,另存为1.reg,然后右键合并,并重启电脑。WindowsRegistryEditorVersion5.00[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\SessionManager\MemoryManagement]"DisablePagingExecutive"=d

三星宣布完成 16 层混合键合堆叠工艺技术验证,有望在 HBM4 内存大面积应用 三星宣布完成 16 层混合键合堆叠工艺技术验证,有望在 HBM4 内存大面积应用 Apr 07, 2024 pm 09:19 PM

报道称,三星电子的高管DaeWooKim表示,在2024年韩国微电子和封装学会年会上,三星电子将完成采用16层混合键合HBM内存技术的验证。据悉,这项技术已通过技术验证。报道还称,此次技术验证将为未来若干年内的内存市场发展奠定基础。DaeWooKim表示,三星电子成功制造了基于混合键合技术的16层堆叠HBM3内存,该内存样品工作正常,未来16层堆叠混合键合技术将用于HBM4内存量产。▲图源TheElec,下同相较现有键合工艺,混合键合无需在DRAM内存层间添加凸块,而是将上下两层直接铜对铜连接,

雷克沙推出 Ares 战神之翼 DDR5 7600 16GB x2 内存套条:海力士 A-die 颗粒,1299 元 雷克沙推出 Ares 战神之翼 DDR5 7600 16GB x2 内存套条:海力士 A-die 颗粒,1299 元 May 07, 2024 am 08:13 AM

本站5月6日消息,雷克沙Lexar推出Ares战神之翼系列DDR57600CL36超频内存,16GBx2套条5月7日0点开启50元定金预售,到手价1299元。雷克沙战神之翼内存采用海力士A-die内存颗粒,支持英特尔XMP3.0,提供以下两个超频预设:7600MT/s:CL36-46-46-961.4V8000MT/s:CL38-48-49-1001.45V散热方面,这款内存套装搭载1.8mm厚度的全铝散热马甲,配备PMIC专属导热硅脂垫。内存采用8颗高亮LED灯珠,支持13种RGB灯光模式,可

消息称三星电子、SK 海力士堆叠式移动内存 2026 年后商业化 消息称三星电子、SK 海力士堆叠式移动内存 2026 年后商业化 Sep 03, 2024 pm 02:15 PM

本站9月3日消息,韩媒etnews当地时间昨日报道称,三星电子和SK海力士的“类HBM式”堆叠结构移动内存产品将在2026年后实现商业化。消息人士表示这两大韩国内存巨头将堆叠式移动内存视为未来重要收入来源,计划将“类HBM内存”扩展到智能手机、平板电脑和笔记本电脑中,为端侧AI提供动力。综合本站此前报道,三星电子的此类产品叫做LPWideI/O内存,SK海力士则将这方面技术称为VFO。两家企业使用了大致相同的技术路线,即将扇出封装和垂直通道结合在一起。三星电子的LPWideI/O内存位宽达512

MIT最新力作:用GPT-3.5解决时间序列异常检测问题 MIT最新力作:用GPT-3.5解决时间序列异常检测问题 Jun 08, 2024 pm 06:09 PM

今天给大家介绍一篇MIT上周发表的文章,使用GPT-3.5-turbo解决时间序列异常检测问题,初步验证了LLM在时间序列异常检测中的有效性。整个过程没有进行finetune,直接使用GPT-3.5-turbo进行异常检测,文中的核心是如何将时间序列转换成GPT-3.5-turbo可识别的输入,以及如何设计prompt或者pipeline让LLM解决异常检测任务。下面给大家详细介绍一下这篇工作。图片论文标题:Largelanguagemodelscanbezero-shotanomalydete

金邦推出全新 DDR5 8600 内存,提供 CAMM2、LPCAMM2 及常规型号可选 金邦推出全新 DDR5 8600 内存,提供 CAMM2、LPCAMM2 及常规型号可选 Jun 08, 2024 pm 01:35 PM

本站6月7日消息,金邦(GEIL)在2024台北国际电脑展上推出了其最新DDR5解决方案,而且给出了SO-DIMM、CUDIMM、CSODIMM、CAMM2和LPCAMM2等版本可选。▲图源:Wccftech如图所示,金邦展出的CAMM2/LPCAMM2内存采用非常紧凑的设计,最高可提供128GB的容量,速度最高可达8533MT/s,其中部分产品甚至可以在AMDAM5平台上稳定超频至9000MT/s,且无需任何辅助散热。据介绍,金邦2024款PolarisRGBDDR5系列内存最高可提供8400

AI 潮影响明显,TrendForce 上修本季度 DRAM 内存、NAND 闪存合约价涨幅预测 AI 潮影响明显,TrendForce 上修本季度 DRAM 内存、NAND 闪存合约价涨幅预测 May 07, 2024 pm 09:58 PM

根据TrendForce的调查报告显示,AI浪潮对DRAM内存和NAND闪存市场带来明显影响。在本站5月7日消息中,TrendForce集邦咨询在今日的最新研报中称该机构调升本季度两类存储产品的合约价格涨幅。具体而言,TrendForce原先预估2024年二季度DRAM内存合约价上涨3~8%,现估计为13~18%;而在NAND闪存方面,原预估上涨13~18%,新预估为15~20%,仅eMMC/UFS涨幅较低,为10%。▲图源TrendForce集邦咨询TrendForce表示,该机构原预计在连续

vivo全新X100系列内存、颜色曝光:全系12+256GB起步 vivo全新X100系列内存、颜色曝光:全系12+256GB起步 May 06, 2024 pm 03:58 PM

5月6日消息,今天vivo官方宣布,全新的vivoX100系列将在5月13日19:00正式发布。据了解,此次发布会预计将发布vivoX100s、vivoX100sPro、vivoX100Ultra三款机型,以及vivo自研影像品牌BlueImage蓝图影像技术。数码博主“数码闲聊站”今天也放出了这三款机型的官方渲染图、内存规格及配色等,其中X100s采用了直屏设计,而X100sPro和X100Ultra则是曲屏设计。该博主透露,vivoX100s共有黑、钛、青、白四种配色,内存规格

See all articles