原子变量和原子操作的使用示例

这个例子是从安卓Android10.0源码logd模块摘取,文件路径:yukawa\system\core\liblog\logd_writer.cpp
这里详细解析什么是原子变量和原子操作,只对源码中对原子变量的运用做一些简单的分析。
这个文件的代码是安卓中log模块接口部分,即应用写log时通过套接字需要将log发送到log服务器,这里是应用client端初始化时对socket进行初始化。对于客户端进程而言,这个初始化过程而言只需要初始化一次就可以了。然而在多线程环境下就有可能造成多次初始化。
对于这种场景可以使用原子变量来避免这个问题。
首先俩看一下socket初始化代码,逻辑很简单:先检查全局的sock文件描述符是否合法,如果合法就意味着这个socket已经初始化过了,可以直接返回;如果socket非法,是一个负值,那就意味着该sock没有初始化,那就创建一个新的socket,并把它的fd赋值给全局的sock。
这个方案在多线程环境下,比如当两个线程同时执行初始化的时候是有可能创建了两个socket。当然我们可以通过加锁来避免这个问题,但是频繁会导致性能收到影响。这个时候可以使用原子变量的原子操作来解决问题。
首先我们可以将sock声明为原子变量,类型为atomic_int,用来保存全局socket的fd,安卓dlog源码定义如下:

union android_log_context_union {
  void* priv;
  atomic_int sock;
  atomic_int fd;
  struct listnode* node;
  atomic_uintptr_t atomic_pointer;
};

android_log_transport_write 这个结构体定义了写log的接口函数指针,其中open函数就是用来做初始化的

struct android_log_transport_write {
  struct listnode node;
  const char* name;                  /* human name to describe the transport */
  unsigned logMask;                  /* mask cache of available() success */
  union android_log_context_union context; /* Initialized by static allocation */

  int (*available)(log_id_t logId); /* Does not cause resources to be taken */
  int (*open)();   /* can be called multiple times, reusing current resources */
  void (*close)(); /* free up resources */
  /* write log to transport, returns number of bytes propagated, or -errno */
  int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec,
               size_t nr);
};

logdOpen()函数的具体实现如下:
sock 先用一个负数-EBADF来初始化,表示该socket还没初始化。


struct android_log_transport_write logdLoggerWrite = {
    .node = {&logdLoggerWrite.node, &logdLoggerWrite.node},
    .context.sock = -EBADF,  //初始化为负数
    .name = "logd",
    .available = logdAvailable,
    .open = logdOpen,
    .close = logdClose,
    .write = logdWrite,
};

/* log_init_lock assumed */
static int logdOpen() {
  int i, ret = 0;

  i = atomic_load(&logdLoggerWrite.context.sock); //原子地加载sock,然后判断sock的值
  if (i < 0) {  //i<0,意味着没有初始化,于是创建一个新的socket
    int sock = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0));
    if (sock < 0) {
      ret = -errno;
    } else {
      struct sockaddr_un un;
      memset(&un, 0, sizeof(struct sockaddr_un));
      un.sun_family = AF_UNIX;
      strcpy(un.sun_path, "/dev/socket/logdw");

      if (TEMP_FAILURE_RETRY(connect(sock, (struct sockaddr*)&un, sizeof(struct sockaddr_un))) <
          0) {
        ret = -errno;
        switch (ret) {
          case -ENOTCONN:
          case -ECONNREFUSED:
          case -ENOENT:
            i = atomic_exchange(&logdLoggerWrite.context.sock, ret);
            [[fallthrough]];
          default:
            break;
        }
        close(sock);
      } else {
        ret = atomic_exchange(&logdLoggerWrite.context.sock, sock); //关键的地方在这里,使用atomic_exchange函数,该函数返回值为原子变量修改前的值
        if ((ret >= 0) && (ret != sock)) {
          close(ret); //如果ret为非负数而且不等于上一步创建的socket的fd,那就意味着这是另一个线程创建的socket,这里需要把它的close掉。
        }
        ret = 0;
      }
    }
  }

  return ret;
}

上面这部分代码使用了atomic_exchange函数,它的声明如下:

C atomic_exchange(volatile A *object, C value);

其中A是原子变量类型,C是对应的值类型,比如A是atomic_int类型,那么C就是int类型.
object是原子变量的指针,value是要给object指向的原子变量所赋的值。
返回值为这个赋值操作执行前object指向对象的值。

源码中关闭socket的代码,当要关闭socket时要给sock赋一个负值,然后再close。那为了避免关闭两次一个socket,这里使用atomic_exchange原子地赋值之后,检查返回值,如果返回值为非负,那就说明该fd之前还没有close,这种情况就可以直接close掉。但如果返回值为负数,那就说明赋值前就已经为负数了,此时就不需要再close。

static void __logdClose(int negative_errno) {
  int sock = atomic_exchange(&logdLoggerWrite.context.sock, negative_errno);
  if (sock >= 0) {
    close(sock);
  }
}

而鸿蒙系统的log模块也是使用类似的处理,其中socket的初始化代码如下。鸿蒙的处理方法是使用了cas对比转换算法,compare_exchange_strong,这个函数意思是只有fdhandle为-1时才会赋值。如果fdhandle不为-1那就意味着已经有其它线程已经进行了初始化,这时候defaultvalue的值就会被更新为fdhandle的值。

namespace OHOS {
namespace HiviewDFX {
int DgramSocketClient::CheckSocket()
{
    if (fdHandler >= 0) {
        return fdHandler;
    }

    int fd = GenerateFD();
    if (fd < 0) {
        return fd;
    }

    int ret;
    int defaultValue = -1;
    fdHandler.compare_exchange_strong(defaultValue, fd); //fdHandler为atomic_int类型
    if (defaultValue != -1) {
        ret = defaultValue;
        close(fd);
    } else {
        ret = fd;
        setHandler(fd);
        Connect();
    }

    return ret;
}
} // namespace HiviewDFX
} // namespace OHOS
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值