目錄
1. 背景
2. 相關知識儲備
思路一: 民科 mtime 檔案最后修改時間
思路二: 科班 作業系統通知特性, 例如 linux 的 inotify
3. 相關代碼設計
3.1 簡單實用版
3.2 嘗試多執行緒
3.3 多執行緒版本
4. 總結
正文
1. 背景
組態檔動態重繪這個業務場景非常常見. 存在兩個主要使用場景, 客戶端和服務器.
客戶端需求很直白, 我本地配置變更, 程式能及時和非及時的重刷到系統中.
服務器相比客戶端做法要多些環節, 服務器本地會有一份配置兜底, 配置中心中配置發生改變會推送給觸發給服務器觸發內部更新操作.
我們這里主要聊場景偏向于客戶端, 本地配置發生改變, 我們如何來更新記憶體中配置.
文章承接于: C中級 - 檔案輔助操作
2. 相關知識儲備
首先思考一個問題我們如何判斷一個檔案發生了更新 ?
這里提供兩種思路.
思路一: 民科 mtime 檔案最后修改時間
struct stat { unsigned long st_dev; /* Device. */ unsigned long st_ino; /* File serial number. */ unsigned int st_mode; /* File mode. */ unsigned int st_nlink; /* Link count. */ unsigned int st_uid; /* User ID of the file's owner. */ unsigned int st_gid; /* Group ID of the file's group. */ unsigned long st_rdev; /* Device number, if device. */ unsigned long __pad1; long st_size; /* Size of file, in bytes. */ int st_blksize; /* Optimal block size for I/O. */ int __pad2; long st_blocks; /* Number 512-byte blocks allocated. */ long st_atime; /* Time of last access. */ unsigned long st_atime_nsec; long st_mtime; /* Time of last modification. */ unsigned long st_mtime_nsec; long st_ctime; /* Time of last status change. */ unsigned long st_ctime_nsec; unsigned int __unused4; unsigned int __unused5; };
在結構體中 st_atime, st_mtime, st_ctime 欄位可以知道, Linux 檔案有三個時間屬性:
1. mtime: 檔案內容最后修改時間
2. ctime: 檔案狀態改變時間, 如權限, 屬性被更改
3. atime: 檔案內容被訪問時間
如 cat, less 等 在默認情況下, ls 顯示出來的是該檔案的 mtime, 即檔案內容最后修改時間.
如果你需要查看另外兩個時間, 可以使用 ls -l --time ctime 命令.
思路二: 科班 作業系統通知特性, 例如 linux 的 inotify
man inotify
inotify_init1 -> inotify_add_watch IN_MODIFY / inotify_rm_watch -> poll 監控機制 -> close
IN_MODIFY (+) File was modified (e.g., write(2), truncate(2)).
流程去注冊關注修改時間, 當檔案發生修改時候作業系統會通知上層應用具體修改詳情.
linux inotify 是一種檔案變化通知機制, 它是一個內核用于通知用戶空間程式檔案系統變化的機制,
以便用戶態能夠及時地得知內核或底層硬體設備發生了什么.
我們這里采用民科思路. linux inotify 對于我們場景有點大材小用了. 歡迎感興趣人參照官方例子去嘗試.
3. 相關代碼設計
3.1 簡單實用版
素材:
https://github.com/wangzhione/structc/blob/9de5200229845c4c7acf921ca63c794918b28fe5/modular/system/file.h
https://github.com/wangzhione/structc/blob/9de5200229845c4c7acf921ca63c794918b28fe5/modular/system/file.c
業務能力設計 file_set 注冊和洗掉, file_update 觸發檢查和更新操作
#pragma once #include "struct.h" #include "strext.h" // // file_f - 檔案更新行為 // typedef void (* file_f)(FILE * c, void * arg); // // file_set - 檔案注冊更新行為 // path : 檔案路徑 // func : NULL 標記清除, 正常 update -> func(path -> FILE, arg) // arg : func 額外引數 // return : void // extern void file_set(const char * path, file_f func, void * arg); // // file_update - 組態檔重繪操作 // return : void // extern void file_update(void);
具體思路是利用 list + mtime , 可以觀察 struct 設計部分
#include "file.h" struct file { time_t last; // 檔案最后修改時間點 char * path; // 檔案全路徑 unsigned hash; // 檔案路徑 hash 值 file_f func; // 執行行為 void * arg; // 行為引數 struct file * next; // 檔案下一個結點 }; static struct file * file_create(const char * path, unsigned h, file_f func, void * arg) { assert(path && func); if (fmtime(path) == -1) { RETURN(NULL, "mtime error p = %s", path); } struct file * fu = malloc(sizeof(struct file)); if (NULL == fu) { return NULL; } fu->last = -1; fu->path = strdup(path); if (NULL == fu->path) { free(fu); return NULL; } fu->hash = h; fu->func = func; fu->arg = arg; // fu->next = NULL; return fu; } inline void file_delete(struct file * fu) { free(fu->path); free(fu); } static struct files { struct file * list; // 當前檔案物件集 } f_s; // files add static void f_s_add(const char * path, unsigned hash, file_f func, void * arg) { struct file * fu = file_create(path, hash, func, arg); if (fu == NULL) { return; } // 直接插入到頭結點部分 fu->next = f_s.list; f_s.list = fu; }
struct file 存盤檔案操作物件, struct files 是 struct file list 集合.
3.2 嘗試多執行緒
我們知道 file_set 和 file_update 不是執行緒安全的. 依賴業務系統啟動時候統一呼叫 file_set 無法運行時修改相關設定.
不知道是否有同學會采用如下設計
static struct files { atomic_flag lock; struct file * list; } f_s;
通過 lock 來保證執行緒安全.
這種思路確實能解決執行緒安全問題, 存在很多缺陷, file_update 業務上面很耗時, 他會阻塞 file_set 操作, 特殊情況會引發業務雪崩.
所以我們需要更針對性鎖.
3.3 多執行緒版本
為了適配多執行緒情況. 首先我們明確下簡單業務, 同步的 file list 就夠用了.
我們這里單純為了沒事要吃蛋炒飯態度, 構造 file dict hash + atomic lock 來沒事找事.
素材:
https://github.com/wangzhione/structc/blob/8c040f0cb3507fc4563bc18f48104f9cc20c5da5/modular/system/file.h
https://github.com/wangzhione/structc/blob/8c040f0cb3507fc4563bc18f48104f9cc20c5da5/modular/system/file.c
總體設計思路
#include "file.h" struct file { time_t last; // 檔案最后修改時間點 file_f func; // 執行行為 void * arg; // 行為引數 }; static struct file * file_create(const char * path, file_f func, void * arg) { assert(path && func); if (fmtime(path) == -1) { RETURN(NULL, "mtime error p = %s", path); } struct file * fu = malloc(sizeof(struct file)); if (NULL == fu) { return NULL; } fu->last = -1; fu->func = func; fu->arg = arg; return fu; } static inline void file_delete(struct file * fu) { free(fu); } struct files { atomic_flag data_lock; // const char * path key -> value struct file // 用于 update 資料 volatile dict_t data; atomic_flag backup_lock; // const char * path key -> value struct file // 在 update 兜底備份資料 volatile dict_t backup; }; static struct files F = { .data_lock = ATOMIC_FLAG_INIT, .backup_lock = ATOMIC_FLAG_INIT, }; extern void file_init() { F.data = dict_create(file_delete); F.backup = dict_create(file_delete); }
我們先在 data 中添加資料, 如果 data 被 update 占用, 我們把資料放入 backup 中再去處理.
// // file_set - 檔案注冊更新行為 // path : 檔案路徑 // func : NULL 標識清除, 正常 update -> func(path -> FILE, arg) // arg : func 額外引數 // return : void // void file_set(const char * path, file_f func, void * arg) { struct file * fu = NULL; assert(path && *path); // step 1 : 嘗試競爭 data lock if (atomic_flag_trylock(&F.data_lock)) { if (NULL != func) { fu = file_create(path, func, arg); } dict_set(F.data, path, fu); return atomic_flag_unlock(&F.data_lock); } // step 2 : data lock 沒有競爭到, 直接競爭 backup lock atomic_flag_lock(&F.backup_lock); fu = file_create(path, func, arg); dict_set(F.backup, path, fu); atomic_flag_unlock(&F.backup_lock); }
4. 總結
去感受其中思路. 我用C寫代碼很順手. 但有時候覺得 C 在現在階段, 不是專業吃這個飯的,
可以嘗試用其它更加高級語言來輕松快捷表達自己的想法和工程版本.
對于開發生涯我花了很多年找到自己定位, 我的底層核心是一名軟體工程師. 然后語言和技術以及商業工程問題陸續通順起來了.
(因為我的單元測驗不充分, 錯誤可能很多, 歡迎在 github 給我提 commit or issure. 時間愉快)
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/412835.html
標籤:C
