首页 > 其他分享 >竞态的一些自己的理解

竞态的一些自己的理解

时间:2022-12-09 00:12:39浏览次数:62  
标签:... TASK THREAD PROCESS LOCK 死锁 理解 一些 竞态

目录

  1. 关于竞态
  2. 并发
  3. 死锁的情况
    1. 单线程死锁
    2. 多线程死锁
  4. 参考资料
关于竞态

我们都知道,竞态的形成即是资源的争用。而资源存在非常多的形式,比如变量,对象, CPU, buffer, 网络,磁盘(文件), 外设的所有权,服务等。

而对资源的不恰当利用可能导致程序低效的运行,资源泄漏,死机,崩溃等。

本文为笔者的一些经验总结,当然也参考了不少文档。试图详尽的介绍与此相关的问题。

当然要做到全面是非常难的,毕竟现实生活中遇到的问题五花八门,完全不知道会在什么时候会出现一个吓你一跳的黑天鹅。

并发

并发,指同一个系统拥有多个计算进程(或者进程),这些进程有同时执行与的潜在交互特性,因此系统会有相当多个执行路径且结果可能具有不确定性。并发计算可能会在具备多核心的同一个芯片中交错运行,以优先分时线程在同一个处理器中执行,或在不同的处理器执行。 -- wikipedia

解决并发过程出现的竞态我所知道的有两个方法:方法之一是给资源上锁;方法二是使用异步单线程方式实现( node.js )。

异步暂时不再此文讨论范畴,因此这里简要介绍一下异步单线程编程的一些需要注意的问题。

  1. 无法利用多核性能。
  2. 需要严格控制过程的是时间片,防止某些过程获取不到资源。
  3. 虽然不要原子性,但是还是需要妥善的对资源进行管理。最好的办法是进入过程获取资源,退出过程释放资源。其次你需要一些管理资源的手段,方便你随时查看当前资源的使用情况(比如说状态变量,计数器);
  4. 不能存在 while 轮询,你只能依靠下一次 CPU 资源什么时候轮到你,因此你也不能够相对精确的在某个时间点去做某件事。这个其实和第 2 点是一致的。

我理解的异步单线程编程即单线程事件编程。

下面是一个用伪代码实现的例子:

    task1_private_data
    task1(){
        ...
        PUSH_TASK( taskX, taskX_private_data )
        ...
    }


    INTRO(){
        // do what you do        
        PUSH_TASK( task1, task1_private_data )
    }

    THREAD{
        INTRO()


        LOOP{
            WAIT_WAKE_UP()
            LOOP{
                GET TASK & TASK_PRIVARE_DATA from TASK_LIST
                CALL TASK( TASK_PRIATE_DATA )
            }
        }
    }

    PUSH_TASK( TASH, TASK_PRIVATE_DATA ){
        PUSH TASK & TASK_PRIATE_DATA PAIRE to TASK_LIST
        WAKE_UP THREAD
    }
死锁的情况 单线程死锁

首先,我们先来讨论一下死锁这个问题。我自己把死锁分成了以下这几个类别:单线程死锁,多线程死锁,不完全死锁。

首先看单线程死锁,单线程死锁的模式如下所示:

    THREAD{
        LOCK A
        ...
        LOCK A
        ...
    }

我觉得有一定经验的人一般不会傻到写出这种代码,但是下面这种情况嘞:

    CLASS A{
        LOCK self
        LOCK_RESOURCE(){
            self_lock.lock()

            return ....
        }

        UNLOCK_RESOURCE(){
            self_lock.unlock()
        }   
    }

    class B{
        A a;
        DO_PROCESS_1(){
            a.LOCK_RESOURCE();
        }
        ...
        DO_PROCESS_n(){
            b.UNLOCK_RESOURCE();
        }

    }

    // 这种例子
    THREAD_1{
        B b;

        b.DO_PROCESS_1();
        ...
        b.DO_PROCESS_1();

    }

    // 还有这种例子
    B b;
    THREAD_2 or PROCESS_2{
        b.DO_PROCESS_1();

        ...
        if CONDITION_1:
            return;
            
        ...
        b.DO_PROCESS_n();
    }

还有一种情况,如下所示:

    // in PROCESS_1
    LOCK *A = new LOCK;

    // in PROCESS_2
    LOCK *B  = A;

    // in PROCESS_3
    delete A;

    // in PROCESS_4
    LOCK B; // 死锁 -- 因为 B 指向的地址已经被释放,因此 B 地址指向的数据可能是任意状态的。

当然如果有这种问题,在项目前期也许很容易暴漏,但如果 THREAD_2 or PROCESS_2 中的 CONDITION_1 很难满足,而你的测试样例又没有覆盖到的话,那么这可能就成为你应用中的一个炸弹了。

上面列举的例子还仅仅是锁,锁的话发现问题了还比较方便定位。如果上面被阻塞的不是锁,而是文件(O_EXCL 或者 "wx" "wbx" "w+x" "wb+x" "w+bx" 模式打开的文件)?一个网络阻塞性的 read 函数?一个可被设置为独占的驱动,外设?那有怎么办嘞?

因此,你不仅仅是需要对你所使用的语言烂熟于心,还必须对你模块中涉及到的所有可能会阻塞或者因条件而阻塞的地方,以及模块于模块之间的业务逻辑(交互逻辑)心知肚明。

多线程死锁

下面我们再来看一看多线程的例子,死锁的通用形式如下所示:

    LOCK a;
    LOCK b;

    THREAD_1{
        ...
        a.lock()
        ...
        b.lock()
        ...
        b.unlock()
        ...
        a.unlock()
        ...
    }

    THREAD_2{
        ...
        b.lock();
        ...
        a.lock();

        ...
        a.unlock();
        ...
        b.unlock();
    }

当然,这些 lock 可能会隐藏再各种判断条件下,或者藏在各种调用的方法过程中,这些设计,可能会将这个模式隐藏德很深。甚至躲过你自信满满的,不完全的测试样例。

再看这个例子,这个例子我们看不到以一个锁,但是 THREAD_3 可能就一直停在那里,(也可能偶尔能运行一下,让你甚是糊涂):

    THREAD_1:{
        LOOP(){
            WAIT LIST_A MESSAGE;
            GET msg FROM LIST_A
            DO SOMETHING 
                PUSH to LIST_B
        }
    }

    THREAD_2:{
        LOOP(){
            WAIT LIST_B MESSAGE;
            GET msg FROM LIST_B
            DO SOMETHING
        }
    }

    THREAD_3{
        PUSH msg to LIST_A
        WAIT msg result from B

        // or
        PUSH msg to LIST_B
        WAIT msg result from A

    }

我从这些例子里面得到的感悟是基础是

1. 千里之行,始于足下。千里之堤,溃于蚁穴。基础是很重要的,细节也很重要。我们需要不断的熟练自己的技能。才能如庖丁解牛,游刃有余。

2. 设计模式不仅仅是书上明确的那些既定的东西,它是一种思维工具, 是刀,是锯,是改锥,也是你自己总结提炼的最佳实践。良好的设计风格是非常重要,多学多想多思考,沉淀出属于自己的一套设计模式是很重要的。

3. 良好设计的关键在于对问题的深入认识,而不是提供了多少高级的特征。 -- 当然更不是不假思索的找了一个解决当前问题的方案即可。因此深入理解业务逻辑是非常重要的。

参考资料
原创文章,版权所有,转载请获得作者本人允许并注明出处 我是留白;我是留白;我是留白;(重要的事情说三遍) TOP BOT

标签:...,TASK,THREAD,PROCESS,LOCK,死锁,理解,一些,竞态
From: https://www.cnblogs.com/mojies/p/16967784.html

相关文章

  • 如何理解垂直布局、水平布局和表单布局?
     整体布局等于垂直布局或水平布局:若将两个垂直布局添加进水平布局窗口内,则窗口整体呈水平布局展示若将两个水平布局添加进垂直布局窗口内,则窗口整体呈垂直布局展示......
  • MAUI新生3.5-深入理解XAML:行为Behavior
    通过行为Behavior,可以将功能附加到控件上,而不需要在宿主控件上定义,和扩展方法有异曲同功之妙。在MAUI中实现Behavior,有两种方式:①附加行为;②MAUI内置行为。附加行为,通过附......
  • 一些最重要的 SQL 命令
    一些最重要的SQL命令SELECT-从数据库中提取数据UPDATE-更新数据库中的数据(updatetestsettest2='A'wheretest1=1;)DELETE-从数据库中删除数据(deletefr......
  • 【matlab编程基础】matlab的一些编程操作
    前言 基本操作1.matlab中如何使用类似字典的方式进行键值操作;ids=[0123456];names={'Unknown','Round','Left','Right','Uturn','Bicycle','Pedes......
  • 浮点数理解梳理
     先说一下计算机中二进制的​​算法​​:· 整数 整数的二进制算法大家应该很熟悉,就是不断的除以2取余数,然后将余数倒序排列。比如求9的二进制: 9/2=4余1 4/2=2余0......
  • Model-Agnostic Meta-Learning (MAML) 理解
    模型不可知元学习(Model-AgnosticMeta-Learning,MAML)的目标是使模型每次的梯度更新更有效、提升模型的学习效率、泛化能力等,它可以被看做一种对模型进行预训练的方法,......
  • Go社区一些约定俗成的规范:
    昨天正巧翻到一位Gopher的博客,发现他写的文章质量都挺高的于是今天早上就一直在看。https://lailin.xyz/post/go-training-week4-project-layout.html正好觉得自己的Gola......
  • 对于CocoaPods的简单理解,实践安装使用过程和常见问题
    (本文是自己通过其他文章进行的自我编辑和简单修改,请大家凑活看看。)一、什么是CocoaPodsCocoaPods是iOS项目的依赖管理工具,该项目源码在Github上管理。开发iOS项目不可避免地......
  • 一些我翻译或原作的文章
    平时有空的话,比较喜欢看外国的.net站点,因此也翻译些好的文章,但限于水平所限,虽有不少已经发表,但恐怕很多也不到位和准确,因此这里整理一下,给大家批评一......
  • (转)理解Heap Profling名词-Shallow和Retained Sizes
    ​​http://rdc.taobao.com/team/jm/archives/900​​​有包含HeapProfling功能的工具(MAT,Yourkit,JProfiler,TPTP等)都会使用到两个名词,一个是S......