↓ 跳过正文
  1. Posts/

用 abstract unix socket 实现进程单实例运行

·802 字·2 分钟·

一,问题背景
#

很多时候,我们需要确保进程只有一个实例运行。

有几种方法:

http://stackoverflow.com/questions/2964391/preventing-multiple-process-instances-on-linux

http://stackoverflow.com/questions/5339200/how-to-create-a-single-instance-application-in-c-or-c

https://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication/src

比较常规的做法,是对一个文件加文件锁 flock,比如对 pid 文件 flock( LOCK_EX|LOCK_NB )

但是这种方法有些弊端:

  1. 如果文件被 mv 或者 rm,是会被绕过的。
  2. 如果磁盘故障比如磁盘满,目录没有写权限,会失败。

二,abstract namespace unix socket
#

http://linux.die.net/man/7/unix

unix socket 有3种:

  1. 基于文件的
  2. socketpair 创建的,匿名的
  3. abstract namespace 的,Linux特有

Linux 下, AF_UNIX socket 支持一种特殊的 abstract namespace unix socket 。

相比 普通的基于文件系统的 unix socket,abstract namespace unix socket :

  1. 没有磁盘文件
  2. 进程挂了以后自动删除,无残留文件
  3. 无需担心与 文件系统上的文件冲突,不需要关心文件系统上的绝对路径是否存在的问题

在 lsof 的结果里面看起来,就是有一些 类似 @test_abstract_ns 这样的 文件项

代码中使用也很简单, abstract namespace unix socket 在 bind 之前,sockaddr_un.sun_path[0] 设成 0x0 即可。

三,代码
#

于是我用 abstract unix socket 实现了一个 SysSem 工具类( 一个 system 范围的 semaphore ), 用来:

  1. 让一个程序只启动一个实例。
  2. 让 x 进程等待 y 进程执行完 yyy 操作后,才能执行 xxx 操作。

特点:

  1. 多进程/线程 并发安全。
  2. 当持有的进程被 kill ,OS自动释放,无残留。
  3. 没有磁盘文件,没有文件意外被删的各种情况。
  4. 不占用 tcp/udp 端口。
  5. 简单,不到 60行代码。

#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#include <algorithm>
#include <string>

//
// a semaphore with system scope.
//
// 1. no race conditions between Post() / GetValue() , better than flock().
// 2. when a running process be killed, automatically release all.
// 3. no file on disk, no accidently delete .
// 4. no tcp/udp socket, no confliction, no port consumption.
//
class SysSem {
public:
    SysSem() : _fd(-1) { memset(&_addr, 0, sizeof(_addr)); }
    ~SysSem();

    void Init(std::string id);

    bool Post();
    bool GetValue();

    const char* GetID() const;

private:
    struct sockaddr_un _addr;
    int _fd;
};

void SysSem::Init(std::string id) {
    _addr.sun_family = AF_UNIX;
    const size_t len = std::min(id.size(), sizeof(_addr.sun_path) - 2);  // 2 = start null and end null byte
    // abstract namespace socket address , _addr.sun_path[0] is a null byte ('\0')
    memcpy(_addr.sun_path + 1, id.c_str(), len);
    // memcpy(_addr.sun_path + 0, id.c_str(), len);
}

const char* SysSem::GetID() const { return &_addr.sun_path[1]; }

SysSem::~SysSem() {
    if (_fd >= 0) {
        ::close(_fd);
        _fd = -1;
    }
}

bool SysSem::Post() {
    _fd = ::socket(AF_UNIX, SOCK_STREAM, 0);
    if (_fd < 0) {
        return false;
    }

    if ((0 != ::bind(_fd, (struct sockaddr*)&_addr, sizeof(_addr))) || (0 != listen(_fd, 65536))) {
        return false;
    }
    return true;
}

bool SysSem::GetValue() {
    const int clientFD = ::socket(AF_UNIX, SOCK_STREAM, 0);
    if (clientFD < 0) {
        return false;
    }
    const bool ret = (0 == ::connect(clientFD, (struct sockaddr*)&_addr, sizeof(_addr)));
    ::close(clientFD);
    return ret;
}

#include <assert.h>
#include <stdio.h>

int main(int argc, char** argv) {
    if (argc != 3) {
        fprintf(stderr, "usage: %s abstract-path post/get\n", argv[0]);
        exit(1);
    }

    SysSem inst;
    inst.Init(argv[1]);

    if (0 == strcasecmp(argv[2], "post")) {
        assert(inst.Post());
        SysSem check;
        check.Init(argv[1]);
        assert(check.GetValue());
        printf("ok, i am the only one under %s. running ...\n", inst.GetID());
        pause();

    } else if (0 == strcasecmp(argv[2], "get")) {
        assert(inst.GetValue());
        printf("a process is running under %s. \n", inst.GetID());
    } else {
        printf("unknown cmd \n");
    }

    return 0;
}

相关文章

Linux LoadAvg 编程比赛

·547 字·2 分钟
问题背景 # https://timyang.net/programming/load-average/ 2015.08.13 高可用架构群 Load 编程比赛,Tim 在群征集一段代码使 load average 最高 多线程写几个 while 循环的方法太 trivial 了,就不提了。 下面是 byronhe 的两个解:

借助 ext4 文件系统的 打洞 功能实现消息队列

·217 字·1 分钟
借助 ext4 文件系统的 打洞 功能,可以实现一个消息队列 https://gist.github.com/CAFxX/571a1558db9a7b393579 fallocate(fd, FALLOC_FL_PUNCH_HOLE|FALLOC_FL_KEEP_SIZE, offset, length); Punching holes in files https://lwn.net/Articles/415889/ 怎样对大文件的中间部分进行增删? 首先,如果复制/写文件的后半段的话,肯定是可以实现的,但是这样不“高效”。 所以这里的需求是 “怎样在大文件中间 insert 或者 delete 一段数据而不用读写文件的后半部分”。

用 DAT 重实现 CppJieba 中文分词算法,降低 99% 内存消耗

·995 字·2 分钟
一,问题背景 # 中文分词应用比较广泛的开源算法,是 jieba 结巴分词,结巴分词较高性能的实现是 C++ 版本的 CppJieba : https://github.com/yanyiwu/cppjieba 在实际使用 CppJieba 的过程中,我们发现 CppJieba 的内存占用比较高。 比如对一个 76W 词 大小 11MB 的词典 ,加载 2份 (比如为了支持平滑改动用户词典)就需要耗费 505MB内存。 这对一些多进程的后台服务,浪费大量内存,难以接受,因此这里希望削减内存耗费。 经过初步调查,确定改进方法,然后动手改造,最终把 505MB 缩减到了 4.7MB ,实现了 99% 内存降低。 此处也有 issue 讨论 https://github.com/yanyiwu/cppjieba/issues/3 代码在 https://github.com/byronhe/cppjieba 。

GB 规模语料上的高性能新词发现算法

·398 字·1 分钟
分词是中文搜索的重要环节,目前分词算法已经比较成熟,分词错误的主要是由于未登录词。 因此发现业务领域语料库中的新词,减少未登录词,对改善搜索引擎的用户体验有重要意义。 新词发现的一种常用算法,是 matrix67 大神 2012 年提出的 《互联网时代的社会语言学:基于SNS的文本数据挖掘》 https://www.matrix67.com/blog/archives/5044 其主要思路,是统计语料中出现的所有 ngram 子字符串的凝固度,自由度,信息熵。 算法中需要统计所有 ngram 子字符串的 左熵右熵,实现该算法时,一般以子字符串为 key,用哈希表来存。 但随着语料库变大时,内存消耗变大, 比如之前的 python 版本实现,对 200MB 的语料,就需要约 30G 内存来存哈希表, 导致单机内存不足无法运行,而且对这样规模的语料库,算法需要跑一两天才能出结果。 这里我应用一些工程实现方面的技巧, 把用哈希表统计左熵右熵的计算,拆分成多个子哈希表,分批计算,并利用多核并行,大幅度优化了算法的性能。

现代密码学实践指南[2015年]

·5739 字·12 分钟
本文介绍目前现代密码学的最先进技术, 前半部分主要翻译自 《Cryptographic Right Answers》,附上收集的资料,和byron个人的理解。 密码学理论艰深,概念繁多,本人知识水平有限,错误难免,如果您发现错误,请务必指出,非常感谢! 下文分类介绍在各种适用场景下,你应该使用的现代密码学算法 # 1. 加密数据 : # 按照优先级,应该选择: (1) 首选 NaCl库,或者libsodium库,使用里面的crypto_secretbox()/crypto_secretbox_open() 函数 (2) Chacha20-Poly1305 算法 (3) AES-GCM 算法 适用场景:当你需要避免把明文数据在网络上传输的时候。 以上3种算法,都是AEAD类的算法,AEAD是2015年最好的选择。 其中的(2)和(3)在结构上类似:一个流加密模式的算法,配合一个多项式结构的MAC。 (2)是一个流加密算法,配合一个为通用cpu优化的MAC算法, 对密码学库的实现者来说,Poly1305也比GCM更容易安全地实现。 AES-GCM是工业标准(TLS目前主要用的就是AES-GCM),现代CPU通常都有专门为AES-GCM设计的硬件指令,但是在没有硬件指令支持的CPU上(比如32位的arm),(3)性能低于(2)。