• NDK 线程同步


    使用场景

    对底层代码进行 HOOK, 不可避免的要考虑多线程同步问题, 当然也可以写个类似 java 的线程本地变量来隔离内存空间。

    死锁分析

    恩, 道理其实大家都懂的, 毕竟大学就学了操作系统,理论神马的窝就不讲了哈, 这里说说我的处理方法。首先线程同步问题主要是多线程对相同的可写内存进行操作导致的, 辣么我们给这些可写的内存,每个内存都加把锁不就得了, 哈哈,就这么简单, 我们将存在多个线程访问的一块内存(比如说一个变量, 一个结构体,一个类)都配把锁, 这样确实就解决线程同步问题了, 但是作为一只程序员,必须打起12分的精神,时刻小心自己是否会掉坑里面去,这里也是哦, 如果按上面这么做,确实解决了多线程数据不一致问题,但是极有可能导致其他问题的出现, 比如死锁, 性能低下等, 当然咱也不需要慌, 上面的设计思路是正确的(可写的多线程共享内存加锁),我们可以添加一些规范来规避这些问题;要想解决一个问题, 我们必须能够知道这个问题发生的原因是什么

    死锁的简介:
    线程A1, A2, 资源 R1, R2, 对应锁 RL1, RL2
    A1的某个操作需要RL1, RL2
    A2的某个操作需要RL2, RL1
    说明: A1在获取RL2锁的时候阻塞了, A2在获取RL1锁的时候阻塞了

    RL1            RL2
    A 1--------------R1-------x------R2
                                        
                                        
    RL2              RL1
    A2  --------------R2-------x-------R1    
    

    嘛, 重上面的分析知道,产生死锁的条件有这些:(窝的看法, 理论的说法请自行百度)

    1. 存在两个以上的锁(这是理所当然的啦, 如果只有一个锁,肿么可能存在死锁问题QAQ, 所以如果多线程共用一个锁,那就大胆的使用, 不用担心死锁啦)
      
    2. 线程获取锁的顺序不一致, 理论一点的说法就是资源存在环形链(可以这么说,如果有多个锁, 但是所有的获得锁的顺序是一样的,那么这些锁其实等价于一个锁, 多块内存也可以看做一个大内存啦, 这个可以用反证法证明)
      

    如何解决死锁

    我的处理方法是这样的(各位大佬有更好的想法求评论区@)
    减少锁嵌套, 也就是临界区的代码量尽量小, 尽量代码分开加锁,比如一个方法可能头部几行代码需要加锁, 和结束的几行需要加同一个锁, 我们就不要将整个方法加锁了, 而是开头加锁,在结尾也加锁, 因为其他代码也有可能加锁了,如果方法整个加锁,辣么锁嵌套就没办法避免了
    不要有循环锁的存在, 比如保持所有锁的获取顺序是相同的,辣么就不会有环路等待的条件啦(我在jni代码里面就是这么处理的)

    linux 互斥锁

    嘛,互斥锁的主要作用是为了让代码进入临界区, 让一段代码可以成为原子操作, 理论上将这个功能只能由操作系统提供, 毕竟操作系统负责任务调度, 管理中断信息, 在 Java 和c++11 中都直接提供了api,c++11 中是 std::mutex, 然而我的 ndk 环境并没有这个 api,所以就用了 linux 系统里面的 api 来实现了。

    互斥锁

    (互斥锁 = synchronized)
    API说明

    //数据结构:
                        pthread_mutex_t
     //    行为:
     //       初始化:
     //          静态初始化:
                            pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
                                            
     //          动态初始化:
                            int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_attr_t *mutexattr);
           
     //          获得锁:
                            int pthread_mutex_lock(pthread_mutex *mutex);
                            int pthread_mutex_trylock(pthread_mutex_t *mutex);
     //          释放锁:
                            int pthread_mutex_unlock(pthread_mutex_t *mutex);
     //          销毁锁:
                            int pthread_mutex_destroy(pthread_mutex *mutex);
      
     // 以上可以发现命名规律, 文件名前缀_抽象的工能名_具体事务
     // 嘛,写c程序的话可以这么命名, 用c++的就不必啦
    

    使用说明

    初始化----加锁--------释放锁------销毁锁

    下面是我在ndk中封装的Lock

    //SimpleLock.h;
       class SimpleLock
       {
           public :
           SimpleLock();
           void lock();
           void unlock();
           ~SimpleLockl();
     
           private:
           pthread_mutex_t __m;
         }
                          
    
      //SimpleLock.cpp
       SimpleLock::SimpleLock()
       {
          __m = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
       }
    
       void SimpleLock::lock()
        {
           int ret = pthread_mutex_lock(&__m);
           assert(ret == 0);
       }
    
       void SimpleLock::unlock()
       {
           int ret = pthread_mutex_unlock(&__m);
           assert(ret == 0);
       }
    
       SimpleLock::~SimpleLock()
       {
           pthread_mutex_destory(&__m);
       }
    

    特别注意

    这里的加锁和解锁应该配对,应该配对,应该配对(重要的事情要说三变) ,最后不用了应该销毁, 应为这部分内存是没办法回收的, 所以说不用的时候要销毁掉,嘛,这里就写到析构函数里面啦, 如果是 c 写的...(额, 别忘了就好)

    bug: 探针的获取ndk数据代码里面也是加了锁的,然而正常情况下没啥问题,如果数据有错, 该方法会直接返回, 但是返回的时候没有解锁!!!最后第二次获取数据的时候程序无响应了!!!

    条件锁

    (本来只想将总结互斥锁,但是条件锁很好玩,也就顺带看一下啦)

    //嘛, 条件锁的作用我理解的就是linux给我们实现的一个简单的观察者。
    //      主要结构:
    //      数据结构:
                                     pthread_cond_t cond_lock;
    //      初始化:
    //      静态:
                                     PTHREAD_COND_INITIALIZER
     
    //      动态: 
                                     int pthread_cond_init(pthread_cond_t *cv,const pthread_condattr_t *cattr)
    //      行为:
    //      等待:
                                     int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
    //      通知:  
                                     int pthread_cond_signal(pthread_cond_t *cv);
    

    使用场景

    在临界区中某个因为满足某个条件而阻塞, 又应为某个条件满足而被唤醒,主要是如果不用条件锁, 如果某个线程需要特定条件才能执行, 而这个条件依赖去其他线程, 那么就要让这个线程隔一段时间查询一下状态,满足则执 行,否则阻塞,这样会导致性能问题, 所以条件锁可以在状态满足条件的时候通知阻塞的线程执行。而且一般来说条件锁和互斥锁要一起用。

         pthread_mutex_t m;
         pthread_cond_t c;
    
    
      线程一:
           pthread_mutex_lock(&m);
            if (条件不满足)
                {
                      pthread_cond_wait(&c, &m);//等待状态满足后再执行
                }
             pthread_mutex_unlock(&m);
                         
      线程二:
            pthread_mutex_lock(&m);
                   ...
            if (条件满足) //线程一可以执行了
               {
                      pthread_cond_signal(&c);
               }
                   ...
            pthread_mutex_unlock(&m);
  • 相关阅读:
    49.把字符串转发为整数(python)
    48.不用加减乘除做加法(python)
    47.1+2+3+...+n(python)
    46.孩子们的游戏(python)
    45.扑克牌顺子(python)
    44.翻转单词序列(python)
    43.左旋转字符串(python)
    42.和为S的两个数字(python)
    41.和为S的连续整数序列(python)
    39.平衡二叉树(python)
  • 原文地址:https://www.cnblogs.com/zhangyan-2015/p/6703133.html
Copyright © 2020-2023  润新知