您的位置:首页 > 编程语言

C基础 如何让代码只执行一次

2018-01-14 16:33 267 查看

1.0 最简单, 最高效的方式

C 代码运行起点 main 就是个大单例函数. 如果把函数注册在其里面, 那么一定很可以 :)

// 某个库需要初始化的函数
void log_init(void) {
... ...
}

int main(int argc, char * argv[]) {
... ...
extern void log_init(void);
log_init()
... ...
return 0;
}

是不是, 很轻松的完成了初始化工作.
不妨赠送一个好用的宏, 用于处理这类事情

//
// EXTERN_RUN - 简单的声明, 并立即使用的宏
// ftest    : 需要执行的函数名称
// ...      : 可变参数, 保留
//
#define EXTERN_RUN(ftest, ...)  \
do {                            \
extern void ftest();        \
ftest (__VA_ARGS__);        \
} while(0)

用起来更简单, 可以插在代码的任何一处

EXTERN_RUN(log_run);

2.0 多线程模式, 如何搞起呢

继续看下面例子 once.c

#include <stdio.h>
#include <pthread.h>

static void _once(void) {
static long _cnt;

printf("_once _cnt = %ld\n", ++_cnt);
}

//
// pthread_once 感受
//
int main(int argc, char * argv[]) {
pthread_once_t once = PTHREAD_ONCE_INIT;

puts("pthread_once 感受开始 ... ");
pthread_once(&once, _once);
pthread_once(&once, _once);
puts("pthread_once 感受结束 ... ");

return 0;
}

gcc -g -Wall -o once.out once.c -lpthread

最终运行结果, 也是如我们所料那样




pthread_once 实际开发中多用于初始化线程私有变量. 其内部实现加锁的.
不妨问个小问题, 如果需要你去实现 pthread_once 你会怎么分析呢 ?

这个问题好解答也不好解答.
核心亮点在于 pthread_once 运行的函数实体崩溃了. 多线程之间如何避免死锁.
不妨参照下面 winds 上面 pthread_once 一位大佬的实现:

#include "pthread.h"
#include "implement.h"

/* [i_a] simple wrapper ensures correct calling convention for all */
static void PTW32_CDECL
ptw32_mcs_lock_cleanup(void *args)
{
ptw32_mcs_local_node_t *node = (ptw32_mcs_local_node_t *)args;
ptw32_mcs_lock_release(node);
}

int
pthread_once (pthread_once_t * once_control, void (PTW32_CDECL *init_routine) (void))
{
if (once_control == NULL || init_routine == NULL)
{
return EINVAL;
}

if ((PTW32_INTERLOCKED_LONG)PTW32_FALSE ==
(PTW32_INTERLOCKED_LONG)PTW32_INTERLOCKED_EXCHANGE_ADD_LONG((PTW32_INTERLOCKED_LONGPTR)&once_control->done,
(PTW32_INTERLOCKED_LONG)0)) /* MBR fence */
{
ptw32_mcs_local_node_t node;

ptw32_mcs_lock_acquire((ptw32_mcs_lock_t *)&once_control->lock, &node);

if (!once_control->done)
{

#if defined(PTW32_CONFIG_MSVC7)
#pragma inline_depth(0)
#endif

pthread_cleanup_push(ptw32_mcs_lock_cleanup, &node);
(*init_routine)();
pthread_cleanup_pop(0);

#if defined(PTW32_CONFIG_MSVC7)
#pragma inline_depth()
#endif

once_control->done = PTW32_TRUE;
}

ptw32_mcs_lock_release(&node);
}

return 0;

}               /* pthread_once */

核心是通过 pthread_cleanup_push 和  pthread_cleanup_pop 解决崩溃死锁问题.
当然还有一种思路, 可以解决上面问题. 不妨往下看.

3.0 跳过锁问题, 尝试原子操作

先举个小例子

#include <stdio.h>

static void _once(void) {
static long _cnt;

printf("_once _cnt = %ld\n", ++_cnt);
}

//
// run once 感受
//
int main(int argc, char * argv[]) {
puts("run once 感受开始 ... ");

{
static int _done;
if (__sync_bool_compare_and_swap(&_done, 0, 1)) {
_once();
}

if (__sync_bool_compare_and_swap(&_done, 0, 1)) {
_once();
}
}

puts("run once 感受结束 ... ");
return 0;
}

运行展示:




这里通过 GCC 提供的 原子交换 __sync_bool_compare_and_swap 解决的.

不妨继续赠送的封装宏, 来完成上面操作

#define ONCE_RUN(code) {                                    \
static int _done;                                       \
if (!_done) {                                           \
if (__sync_bool_compare_and_swap(&_done, 0, 1)) {   \
code                                            \
}                                                   \
}                                                       \
}

因为是原子操作, 没有锁那么重, 自然出了问题也不会引起死锁问题.

当然有人说 pthread, __sync_xxx 都是和 GCC 绑定的, 那么 CL 能不能使用了. 当然也是可以的.

pthread 跨平台 - https://github.com/wangzhione/structc/blob/master/structc/system/thread.h
atomic 跨平台 - https://github.com/wangzhione/atomic/blob/master/atomic/atom.h
通过上面基础封装库支持, 用 C 写系统应用相关代码还是很好搞的.


把酒满上, 还能再写几行代码

有问题, 是肯定的. 欢迎指正, 更新是共同提高的过程.

老鼠爱大米 - http://music.163.com/m/song?id=308789&userid=16529894
回头看看, 时间好快呀.  那些个一块补习, 为了玩的伙伴们, 二胎孩子都快上学了.
哈哈.
咱们一线秃头兵还在为了 穷 而冲锋陷阵 (°ー°〃)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: