写点什么

多线程、多进程同时操作 MMAP,会怎么样?

作者:C++后台开发
  • 2022-10-28
    湖南
  • 本文字数:12839 字

    阅读完需:约 42 分钟

多线程、多进程同时操作MMAP,会怎么样?

今天我们来聊聊同步,假设一种场景,就是在客户端 Activity 中,开启多个线程同时写入 mmap 内存中,看看会怎样

多线程写入

private fun testMultiThreadWriteMMIPC() {    "set data".print(getProcessName())    val countDownLatch = CountDownLatch(2)    Thread {        var index = 10000        //重复写入一万次        repeat(10000) {            index++            MMIPC.setData(index.toString(), index.toString())        }        countDownLatch.countDown()    }.start()    Thread {        var index = 20000        //重复写入一万次        repeat(10000) {            index++            MMIPC.setData(index.toString(), index.toString())        }        countDownLatch.countDown()    }.start()    //等待两个线程结束    countDownLatch.await()    //打印写入数据的长度    MMIPC.getData("").length.toString().print(getProcessName())}
复制代码

输出日志

2022-07-20 22:20:46.743 9933-9933/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-20 22:20:46.744 9933-9933/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-20 22:20:46.744 9933-9933/com.zzy.mmipc E/MMIPC->: pid[9933]2022-07-20 22:20:46.744 9933-9933/com.zzy.mmipc D/MMIPC->: open m_fd[78], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-20 22:20:46.744 9933-9933/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-20 22:20:46.744 9933-9933/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41615362022-07-20 22:20:46.744 9933-9933/com.zzy.mmipc D/MMIPC->: mmap success2022-07-20 22:20:46.845 9933-9933/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-20 22:20:46.913 9933-9933/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 237004
复制代码

正常长度等于 20000*12 = 240000,因为 setData,我会将 key 和 value 进行预处理变成如下,所以一次 setData 的长度是 12,那么重试两万次就是 24 万

string content = key + ":" + value + ",";
复制代码

有什么办法可以解决该问题呢,往下看

HandlerThread

利用 Looper 机制,实现单线程中让所有的任务按顺序执行,直接代码验证效果,创建一个 dev-looper 分支,然后代码改变如下

然后测试结果如下,这次是 240000

2022-07-20 22:43:47.874 11336-11336/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-20 22:43:47.897 11336-11336/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-20 22:43:47.897 11336-11336/com.zzy.mmipc E/MMIPC->: pid[11336]2022-07-20 22:43:47.897 11336-11336/com.zzy.mmipc D/MMIPC->: open m_fd[76], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-20 22:43:47.897 11336-11336/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-20 22:43:47.897 11336-11336/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41615362022-07-20 22:43:47.897 11336-11336/com.zzy.mmipc D/MMIPC->: mmap success2022-07-20 22:43:48.265 11336-11336/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-20 22:43:56.241 11336-11336/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 240000
复制代码

我们想想,这样实现有什么问题,功能没什么问题,但失去了原本用 C++实现的好处,那就是跨平台能力,如果再 IOS 端,我们如何实现呢?接下来我们考虑用 C++的方式实现对多线程写入

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

【文章福利】另外还整理一些 C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以自行添加:Q群:720209036 点击加入~ 群文件共享

互斥锁(mutex)

该锁限制同一时间只有一个线程访问数据,实现如下,下面跨进程的时候在详细了解 mutex

//声明pthread_mutex_t m_lock;//setData函数中使用void MMIPC::setData(const string &key, const string &value) {    //加锁    pthread_mutex_lock(&m_lock);    string content = key + ":" + value + ",";//    ALOGD("setData content=%s", content.c_str());    size_t numberOfBytes = content.length();    if (m_position + numberOfBytes > m_file_size) {        auto msg = "m_position: " + to_string(m_position) + ", numberOfBytes: " +                   to_string(numberOfBytes) +                   ", m_file_size: " + to_string(m_file_size);        throw out_of_range(msg);    }    m_position = strlen(m_ptr);    memcpy(m_ptr + m_position, (void *) content.c_str(), numberOfBytes);//    ALOGD("setData success m_ptr.len=%d", m_position + numberOfBytes);    //释放锁    pthread_mutex_unlock(&m_lock);}
复制代码

测试日志

2022-07-20 23:04:10.958 12322-12322/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-20 23:04:10.959 12322-12322/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-20 23:04:10.959 12322-12322/com.zzy.mmipc E/MMIPC->: pid[12322]2022-07-20 23:04:10.959 12322-12322/com.zzy.mmipc D/MMIPC->: open m_fd[78], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-20 23:04:10.959 12322-12322/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-20 23:04:10.959 12322-12322/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41615362022-07-20 23:04:10.959 12322-12322/com.zzy.mmipc D/MMIPC->: mmap success2022-07-20 23:04:11.047 12322-12322/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-20 23:04:11.187 12322-12322/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 240000
复制代码

同样得到正确的结果,结果一样,哪种方式更好呢?这个给你留个作业,自己去验证下两种实现方式的耗时,用耗时来判断哪种更好,我初步判断是差不多的,因为 Looper 循环起来也是一个个处理的,而这个锁也一样,处理完一个,就处理下一个,但上锁和释放锁是有一定耗时的,估计在量上去后 Looper 更优秀。所以后面我们验证完了以后,可以用 C++实现一个 Looper,这样既可以做到跨平台,也可以做到不用锁。这期我们先跳过,优先实现多进程的读写,那么多进程有哪些方式实现同步呢?

多进程写

通过资料的查询,目前找到几种方案,分别是 Semaphores 和 Mutex 以及文件锁,下面就一一实践。

Semaphores

信号量提供了一种有效的进程间通信形式。协作进程可以使用信号量来同步对资源的访问,最常见的是共享内存。信号量还可以保护以下可供多个进程使用的资源免受不受控制的访问

  • 全局变量,例如文件变量、指针、计数器和数据结构。保护这些变量,防止多个进程同时访问。

  • 硬件资源,例如磁盘和磁带驱动器。硬件资源需要受控访问,因为同时访问会导致数据损坏。

看过介绍,发现它可以保护资源被多个进程同时访问,所以先来对它有个详细的了解,然后再用代码实战一下,看看效果。

概述

信号量用于控制进程对共享资源的访问。计数信号量有一个正整数值,表示可以同时锁定信号量的进程数。

分类

  • 命名信号量,命名信号量提供对多个进程之间资源的访问。

  • 未命名信号量,未命名的信号量提供对单个进程内或相关进程之间的资源的多种访问。

一些信号量函数专门设计用于对命名或未命名信号量执行操作。

如何操作

  • 信号量的值为正数,则锁定

  • 信号量值递减,进程继续执行,如果信号量的值为零或负数,则请求锁的进程将等待(被阻塞),直到另一个进程解锁该资源。

可能会阻塞多个进程以等待资源变得可用。

另外

信号量是全局实体,不与任何特定进程相关联。从这个意义上说,信号量没有所有者,因此无法出于任何目的(例如,错误恢复)跟踪信号量的所有权。

仅当使用共享资源的所有进程通过在不可用时等待信号量并在放弃资源时增加信号量值来合作时,信号量保护才起作用。由于信号量缺乏所有者,因此无法确定其中一个合作进程是否已变得不合作。使用信号量的应用程序必须仔细详细说明协作任务。共享资源的所有进程必须就哪个信号量控制资源达成一致。

接口


实战

通过上面的了解,我们先来改造下项目,实现一个跨进程写入的逻辑,如下:

子进程 Activity 改造

这样改造后,就可以实现多进程同时写操作,我们运行看下结果

2022-07-20 23:59:40.605 16531-16531/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-20 23:59:40.606 16531-16531/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-20 23:59:40.606 16531-16531/com.zzy.mmipc E/MMIPC->: pid[16531]2022-07-20 23:59:40.606 16531-16531/com.zzy.mmipc D/MMIPC->: open m_fd[78], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-20 23:59:40.606 16531-16531/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-20 23:59:40.606 16531-16531/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41615362022-07-20 23:59:40.606 16531-16531/com.zzy.mmipc D/MMIPC->: mmap success2022-07-20 23:59:40.704 16531-16531/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-20 23:59:40.860 16531-16531/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 2400002022-07-20 23:59:40.896 16564-16564/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: init2022-07-20 23:59:40.897 16564-16564/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-20 23:59:40.897 16564-16564/com.zzy.mmipc E/MMIPC->: pid[16564]2022-07-20 23:59:40.897 16564-16564/com.zzy.mmipc D/MMIPC->: open m_fd[78], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-20 23:59:40.897 16564-16564/com.zzy.mmipc D/MMIPC->: m_fd size[4161536]2022-07-20 23:59:40.897 16564-16564/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 4161536, default_mmap_size 41615362022-07-20 23:59:40.897 16564-16564/com.zzy.mmipc D/MMIPC->: mmap success2022-07-20 23:59:46.110 16564-16564/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: 360000
复制代码

发现由于 MainActivity,写入太快了,导致后面子进程,初始化完成前已经写入完整,那我们加大 MainActivity 的写入次数。

2022-07-21 00:06:43.077 17809-17809/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-21 00:06:43.080 17809-17809/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-21 00:06:43.080 17809-17809/com.zzy.mmipc E/MMIPC->: pid[17809]2022-07-21 00:06:43.080 17809-17809/com.zzy.mmipc D/MMIPC->: open m_fd[76], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-21 00:06:43.080 17809-17809/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-21 00:06:43.080 17809-17809/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41615362022-07-21 00:06:43.081 17809-17809/com.zzy.mmipc D/MMIPC->: mmap success2022-07-21 00:06:43.472 17809-17809/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-21 00:06:43.600 17860-17860/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: init2022-07-21 00:06:43.602 17860-17860/com.zzy.mmipc D/MMIPC->: open file /data/user/0/com.zzy.mmipc/files2022-07-21 00:06:43.602 17860-17860/com.zzy.mmipc E/MMIPC->: pid[17860]2022-07-21 00:06:43.602 17860-17860/com.zzy.mmipc D/MMIPC->: open m_fd[76], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-21 00:06:43.602 17860-17860/com.zzy.mmipc D/MMIPC->: m_fd size[4161536]2022-07-21 00:06:43.602 17860-17860/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 4161536, default_mmap_size 41615362022-07-21 00:06:43.603 17860-17860/com.zzy.mmipc D/MMIPC->: mmap success2022-07-21 00:07:05.648 17860-17860/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: 17445482022-07-21 00:07:07.888 17809-17809/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 1798260
复制代码

可能是我手机性能太好,加了线程同步后,发现多进程写的时候并没有乱,于是我加大了重试次数,终于不负有心人,终于出错了,目前是重试 15 万次,正常应该是 15*12 = 180 万长度才对,打印的最终结果是 1798260,差了 1740,接下来我们想办法找回来

先用 Semaphores 试试,看能否找回 1740,抱歉没实现成功,我们来用 mutex 试下

跨进程同步 Mutex

开发中,Mutex 能够保证多个线程对同一共享资源的互斥访问。互斥体(互斥对象)是一个程序对象,它被创建以便多个程序线程可以轮流共享同一资源,通常,当程序启动时,它会在开始时通过向系统请求给定资源来为给定资源创建互斥体,并且系统会为其返回唯一的名称或 ID。之后,任何需要该资源的线程都必须在使用该资源时使用互斥锁从其他线程锁定该资源。如果互斥锁已被锁定,则需要资源的线程通常由系统排队,然后在互斥锁解锁时获得控制权(再次,互斥锁在新线程使用资源期间被锁定)。

如果这个互斥对象我们使用共享内存创建,那就可以实现跨进程锁。那么如何创建呢?

struct shm_mutex {    pthread_mutex_t mutex;    pthread_mutexattr_t mutexattr;};
inline void ShmMutex::createShmMutex(string dir) { int fd = open(dir.c_str(), O_CREAT | O_RDWR, 0666); if (fd == -1) { ALOGD("open fail for mutex: fd[%d], %s", fd, dir.c_str()); return; } ALOGD("open success for mutex: fd[%d], %s", fd, dir.c_str()); ftruncate(fd, sizeof(struct shm_mutex)); shmMutex = static_cast<shm_mutex *>(mmap(NULL, sizeof(struct shm_mutex), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); if (shmMutex == MAP_FAILED) { ALOGD("mutex mmap failed"); return; } if (close(fd) == 0) { fd = -1; } else { //fail to close ALOGD("mutex fd close failed"); } memset(shmMutex, 0, sizeof(struct shm_mutex)); pthread_mutexattr_init(&shmMutex->mutexattr); pthread_mutexattr_setpshared(&shmMutex->mutexattr, PTHREAD_PROCESS_SHARED); pthread_mutex_init(&shmMutex->mutex, &shmMutex->mutexattr); pthread_mutexattr_destroy(&shmMutex->mutexattr);}
复制代码

我们通过 mmap 创建一个共享内存,然后通过 pthread_mutexattr_init 初始化共享内存中的 mutexattr,再设置 PTHREAD_PROCESS_SHARED 跨进程互斥锁,最终 pthread_mutex_init 初始化 共享互斥对象 mutex。这样多个进程就可以通过一个 mutex 对象来实现互斥。上面的逻辑只是初始化,怎么使用呢?

第一 ShmMutex mLock;第二 在原来加锁的地方,加上ShmMutex锁void MMIPC::setData(const string &key, const string &value) {    AutoMutex autoMutex(mLock);    string content = key + ":" + value + ",";//    ALOGD("setData content=%s", content.c_str());    size_t numberOfBytes = content.length();    if (m_position + numberOfBytes > m_file_size) {        auto msg = "m_position: " + to_string(m_position) + ", numberOfBytes: " +                   to_string(numberOfBytes) +                   ", m_file_size: " + to_string(m_file_size);        throw out_of_range(msg);    }    m_position = strlen(m_ptr);    memcpy(m_ptr + m_position, (void *) content.c_str(), numberOfBytes);//    ALOGD("setData success m_ptr.len=%d", m_position + numberOfBytes);}
复制代码

你是不是发现了 AutoMutex,这里用到一个黑科技,想想我们之前加锁是这样

pthread_mutex_lock(&count_mutex);pthread_mutex_unlock(&count_mutex);
复制代码

lock、unlock 必须成对出现的,为什么呢?因为如果你加了锁之后,不释放,就会导致该资源无法被别人使用,从而产生死锁反应,那 autoMutex 怎么实现的自动释放呢?来为你揭晓答案

class ShmMutex {public:    enum {        PRIVATE = 0,        SHARED = 1    };
ShmMutex();
~ShmMutex();
// lock or unlock the mutex status_t lock();
void unlock();
// lock if possible; returns 0 on success, error otherwise status_t tryLock();
/** * 该方法,创建共享内存的Mutex,实现跨进程互斥锁 * @param dir */ void createShmMutex(string dir);
// Manages the mutex automatically. It'll be locked when Autolock is // constructed and released when Autolock goes out of scope. class Autolock { public: inline Autolock(ShmMutex &mutex) : mLock(mutex) { mLock.lock(); }
inline Autolock(ShmMutex *mutex) : mLock(*mutex) { mLock.lock(); }
inline ~Autolock() { mLock.unlock(); }
private: ShmMutex &mLock; };
private:
ShmMutex(const ShmMutex &);
ShmMutex &operator=(const ShmMutex &);
struct shm_mutex *shmMutex;
};
复制代码

你看~Autolock()析构函数就明白了,它利用了 C++的构造和析构函数特性,也就是说析构函数在对象被销毁时自动调用,而我们在 setData 中,autoMutex 使用的栈内存,所以当函数出栈时,会自动释放栈内的对象。这样就完美实现了成对的锁。牛吹了半天,下面是验证的时候了,跑下封装好的代码,看看是否是正常输出 180 万的长度,请看

2022-07-23 18:44:31.241 12534-12534/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc D/MMIPC->: root dir /data/user/0/com.zzy.mmipc/files2022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc D/MMIPC->: open success for mutex: fd[78], /data/user/0/com.zzy.mmipc/files/default_mutex.ipc2022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc E/MMIPC->: pid[12534]2022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc D/MMIPC->: open m_fd[78], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41615362022-07-23 18:44:31.242 12534-12534/com.zzy.mmipc D/MMIPC->: mmap success2022-07-23 18:44:31.369 12534-12534/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-23 18:44:31.472 12575-12575/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: init2022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc D/MMIPC->: root dir /data/user/0/com.zzy.mmipc/files2022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc D/MMIPC->: open success for mutex: fd[76], /data/user/0/com.zzy.mmipc/files/default_mutex.ipc2022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc E/MMIPC->: pid[12575]2022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc D/MMIPC->: open m_fd[76], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc D/MMIPC->: m_fd size[4161536]2022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 4161536, default_mmap_size 41615362022-07-23 18:44:31.473 12575-12575/com.zzy.mmipc D/MMIPC->: mmap success2022-07-23 18:44:34.273 12534-12534/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 11924642022-07-23 18:44:42.746 12575-12575/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: 1800000
复制代码

看到了没,1800000,完美实现跨进程同步。但这样就结束了吗?由于我们使用了跨进程的互斥锁,如果该进程在上锁后,并且未释放之前,进程崩溃了怎么办?咋整?我们能不能做到在进程崩溃前,自动释放呢?通过搜寻,我找到了答案,我们可以使用

pthread_mutexattr_setrobust() ,将 pthread 互斥锁初始化为“robust”,如果持有互斥锁的进程死了,下一个获取它的线程将收到 EOWNERDEAD(但仍然成功获取互斥锁),以便它知道执行任何清理。然后它需要使用 pthread_mutex_consistent() 通知获取的互斥锁再次一致。但一个不好的消息,如图:

那就是 android 的 ndk,没有这个函数,而且我看了 google 官方项目 Issue 对这个做了回答: github.com/android/ndk…,意思是目前还没支持。怎么办呢?现在只剩下最后一条路,那就是文件锁了。

文件锁 flock

在 linux 系统中,flock 函数是为解决多进程对同一文件的读写冲突的,而 flock 函数只能锁定整个文件,无法锁定文件的某一区域。且 flock 可以保证 robust,这也是我们选择它最终目的。

int flock(int fd,int operation);
复制代码
  • fd 文件描述

  • operation 锁的模式

  • LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。

  • LOCK_EX 建立互斥锁定。多个进程中一个文件同时只有一个互斥锁定。

  • LOCK_UN 解除文件锁定状态。

  • LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与 LOCK_SH 或 LOCK_EX 做组合

  • 单一文件无法同时建立共享锁定和互斥锁定,且当使用 dup()或 fork()时文件描述符也不会继承此种锁定。

  • 返回值 返回 0 表示成功,若有错误则返回-1,错误代码存于 errno。

代码实现

void MMIPC::setData(const string &key, const string &value) {//    AutoMutex autoMutex(m_mutex_lock);    // 上跨进程写锁    flock(m_fd, LOCK_EX);    string content = key + ":" + value + ",";//    ALOGD("setData content=%s", content.c_str());    size_t numberOfBytes = content.length();    if (m_position + numberOfBytes > m_file_size) {        auto msg = "m_position: " + to_string(m_position) + ", numberOfBytes: " +                   to_string(numberOfBytes) +                   ", m_file_size: " + to_string(m_file_size);        throw out_of_range(msg);    }    m_position = strlen(m_ptr);    memcpy(m_ptr + m_position, (void *) content.c_str(), numberOfBytes);//    ALOGD("setData success m_ptr.len=%d", m_position + numberOfBytes);    // 解锁    flock(m_fd, LOCK_UN);}
复制代码

运行效果

2022-07-24 12:54:32.259 10191-10191/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-24 12:54:32.521 10221-10221/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: init2022-07-24 12:54:32.523 10221-10221/com.zzy.mmipc D/MMIPC->: root dir /data/user/0/com.zzy.mmipc/files2022-07-24 12:54:32.524 10221-10221/com.zzy.mmipc D/MMIPC->: open success for mutex: fd[76], /data/user/0/com.zzy.mmipc/files/default_mutex.ipc2022-07-24 12:54:32.524 10221-10221/com.zzy.mmipc E/MMIPC->: pid[10221]2022-07-24 12:54:32.524 10221-10221/com.zzy.mmipc D/MMIPC->: open m_fd[76], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-24 12:54:32.524 10221-10221/com.zzy.mmipc D/MMIPC->: m_fd size[4194304]2022-07-24 12:54:32.524 10221-10221/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 4194304, default_mmap_size 41943042022-07-24 12:54:32.524 10221-10221/com.zzy.mmipc D/MMIPC->: mmap success2022-07-24 12:54:44.677 10221-10221/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: 27965402022-07-24 12:55:52.774 10191-10191/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 3600000
复制代码

怎么是 36 万?不应该是 18 万么,是的,因为我跑了两遍,一遍不放心不是,万一出问题呢。接着之前的经验,为了避免自己手欠无法正常的配对使用锁,我们封装下文件锁 flock,如下

//// Created by 张占永 on 2022/7/24.//
#include <fcntl.h>#include <sys/file.h>#include <unistd.h>#include <cerrno>#include "AndroidLog.h"
#ifndef MMIPC_FILELOCK_H#define MMIPC_FILELOCK_H

enum LockType { SharedLockType, ExclusiveLockType,};
class FileLock { int m_fd;
bool isFileLockValid() { return m_fd >= 0; }
public: explicit FileLock(int fd);
bool lock(LockType lockType);
bool try_lock(LockType lockType);
bool unlock();
// just forbid it for possibly misuse explicit FileLock(const FileLock &other) = delete;
FileLock &operator=(const FileLock &other) = delete;
class Autolock { public: inline Autolock(FileLock &flock, LockType lockType) : mFLock(flock), m_lockType(lockType) { mFLock.lock(m_lockType); }
inline Autolock(FileLock *flock, LockType lockType) : mFLock(*flock), m_lockType(lockType) { mFLock.lock(m_lockType); }
inline ~Autolock() { mFLock.unlock(); }
private: FileLock &mFLock; LockType m_lockType; };
};
inline FileLock::FileLock(int fd) : m_fd(fd) {}
inline bool FileLock::lock(LockType lockType) { if (!isFileLockValid()) { return false; } int res = flock(m_fd, lockType == SharedLockType ? LOCK_SH : LOCK_EX); if (res == -1) { ALOGD("flock lock fail: fd[%d]", m_fd); } return res == 0;}
inline bool FileLock::try_lock(LockType lockType) { if (!isFileLockValid()) { return false; } int res = flock(m_fd, lockType == SharedLockType ? LOCK_SH | LOCK_NB : LOCK_EX | LOCK_NB); if (res == -1) { ALOGD("flock try_lock fail: fd[%d]", m_fd); } return res == 0;}
inline bool FileLock::unlock() { if (!isFileLockValid()) { return false; } int res = flock(m_fd, LOCK_UN); if (res == -1) { ALOGD("flock unlock fail: fd[%d]", m_fd); } return res == 0;}
typedef FileLock::Autolock AutoFileLock;
#endif //MMIPC_FILELOCK_H
复制代码

然后调用如下

void MMIPC::setData(const string &key, const string &value) {//    AutoMutex autoMutex(m_mutex_lock);    // 加文件锁    AutoFileLock autoFileLock(m_file_lock, ExclusiveLockType);    string content = key + ":" + value + ",";//    ALOGD("setData content=%s", content.c_str());    size_t numberOfBytes = content.length();    if (m_position + numberOfBytes > m_file_size) {        auto msg = "m_position: " + to_string(m_position) + ", numberOfBytes: " +                   to_string(numberOfBytes) +                   ", m_file_size: " + to_string(m_file_size);        throw out_of_range(msg);    }    m_position = strlen(m_ptr);    memcpy(m_ptr + m_position, (void *) content.c_str(), numberOfBytes);//    ALOGD("setData success m_ptr.len=%d", m_position + numberOfBytes);}
复制代码

运行一下,看看如何

2022-07-24 13:36:29.715 28670-28670/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: init2022-07-24 13:36:29.716 28670-28670/com.zzy.mmipc D/MMIPC->: root dir /data/user/0/com.zzy.mmipc/files2022-07-24 13:36:29.716 28670-28670/com.zzy.mmipc D/MMIPC->: open success for mutex: fd[75], /data/user/0/com.zzy.mmipc/files/default_mutex.ipc2022-07-24 13:36:29.716 28670-28670/com.zzy.mmipc E/MMIPC->: pid[28670]2022-07-24 13:36:29.717 28670-28670/com.zzy.mmipc D/MMIPC->: open m_fd[75], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-24 13:36:29.717 28670-28670/com.zzy.mmipc D/MMIPC->: m_fd size[0]2022-07-24 13:36:29.717 28670-28670/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 0, default_mmap_size 41943042022-07-24 13:36:29.717 28670-28670/com.zzy.mmipc D/MMIPC->: mmap success2022-07-24 13:36:29.929 28670-28670/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: set data2022-07-24 13:36:30.414 28737-28737/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: init2022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc D/MMIPC->: root dir /data/user/0/com.zzy.mmipc/files2022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc D/MMIPC->: open success for mutex: fd[76], /data/user/0/com.zzy.mmipc/files/default_mutex.ipc2022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc E/MMIPC->: pid[28737]2022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc D/MMIPC->: open m_fd[76], /data/user/0/com.zzy.mmipc/files/default_mmap.ipc2022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc D/MMIPC->: m_fd size[4194304]2022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc D/MMIPC->: getFileSize m_file_size 4194304, default_mmap_size 41943042022-07-24 13:36:30.415 28737-28737/com.zzy.mmipc D/MMIPC->: mmap success2022-07-24 13:36:38.311 28737-28737/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc:childProcess 日志: 14635202022-07-24 13:36:51.944 28670-28670/com.zzy.mmipc D/MMIPC->: 进程:com.zzy.mmipc 日志: 1800000
复制代码

好的,18 万,完美。是不是可结束了?其实光有 flock 是不行的,还需要线程同步,这里又用到了 mutex 的线程锁,可以往上翻一翻看下,今天就到此结束。

总结

多线程如何保持结果一致性,我们做了两种实现方式:

  • Looper 实现

  • mutex 互斥锁

多进程如何保持结果一致性,我们做了三种:

  • Semaphores 找了大量的示例,没有测试成功,抱歉

  • mutext 实现了跨进程的互斥锁,但它无法保证 robust,意思是在进程出现异常死掉时,无法释放锁,导致其他进程无法再次获取,从而产生不可预估的问题

  • flock 实现了跨进程的文件锁,但需要配合 mutex 线程锁来解决多线程问题

总之,目前为止,我们搞定了 mmap 的跨线程以及跨进程同步的问题,接下来就可以考虑如何设计传输的数据结构,下期我们来用 pb 做载体,体验 pb 的魅力。


原文链接:https://juejin.cn/post/7123828026722222094

用户头像

C/C++后台开发技术交流qun:720209036 2022-05-06 加入

还未添加个人简介

评论

发布
暂无评论
多线程、多进程同时操作MMAP,会怎么样?_多线程_C++后台开发_InfoQ写作社区