一.skynet的安裝編譯
skynet:在ubuntu16.0.4環境下的安裝:
apt-get install git build-essential libreadline-dev autoconf (for ubuntu 16.04)
git clone https://github.com/cloudwu/skynet.git
cd skynet
make linux
Make linux出現錯誤:
error: RPC failed; curl 18 transfer closed with outstanding read data remaining
fatal: The remote end hung up unexpectedly
fatal: early EOF
fatal: index-pack failed
fatal: clone of 'https://github.com/jemalloc/jemalloc.git' into submodule path '3rd/jemalloc' failed
Makefile:41: recipe for target '3rd/jemalloc/autogen.sh' failed
make[1]: *** [3rd/jemalloc/autogen.sh] Error 128
make[1]: Leaving directory '/home/will/myshare/skynet_root/skynet'
platform.mk:40: recipe for target 'linux' failed
make: *** [linux] Error 2
error: RPC failed; curl 18 transfer closed with outstanding read data remaining解決方法:
1.緩存區溢出curl的postBuffer的默認值太小,需要增加緩存--使用git命令增大緩存(單位是b,524288000B也就500M左右)
git config --global http.postBuffer 524288000
2.修改下載速度--修改下載速度
git config --global http.lowSpeedLimit 0
git config --global http.lowSpeedTime 999999
git config --list
git config --global core.compression 9
其他方法:更新遠程庫到本地
git clone --depth=1 http://xxx.git
git fetch --unshallow
針對報錯,我的解決方案:cd ./3rd,手動git clone jemalloc源碼,再make linux進行編譯。
cd ./3rd
git clone https://github.com/jemalloc/jemalloc.git
git clone --depth=1 https://github.com/jemalloc/jemalloc.git
二.skynet介紹:
Skynet:是一個輕量級的后台服務器框架;
應用場景:金融,證券,股票,游戲;
多核開發:
多核開發解決方案有:
1,多線程解決方案;
2,多進程解決方案;
3,CSP解決方案,由Go語言開發,goroutine協程+channel管道--加強版的多線程解決方案;
4,還有actor解決方案--也就是加強版的多進程解決方案;
實際多線程與語言層面抽象出來的的多線程區別:
實際的多進程/多線程:系統調度多進程。socket多台機器間,多進程間,傳遞不穩定;
語言層面抽象出來的多進程,由skynet/erlang調度actor。skynet中通過消息(指針)傳遞,而指針傳遞的方式是穩定的;推薦的使用方法是在一個進程中解決所有的問題。
1,多進程模型,隔離性(運行環境)好,統一性差;(考慮進程間通信的問題)
2,多線程模型,隔離性差,統一性強;(考慮線程同步的問題)。多線程采用鎖機制訪問臨界資源;應用中注意鎖粒度;
解決多進程模型中數據一致:常用socket, 共享內存mmap,管道, 信號量
數據一致解決方案有:
1)消息隊列:強調的是通知,最終一致性的問題; zeromq推拉模型,請求回應,監聽回應,
協議問題;斷線重連;進程啟動順序的問題;負載均衡問題(負載均衡:fd%n);數據同步的問題(一致性HASH);
2)數據同步的問題解決方案之Rpc, 強一致性問題,強調處理結果。
3)進程協調管理,Zoomkeeper服務協調的問題(數據模型+監聽機制)
多個進程競爭有限資源的時候,解決方案:
1,數據中心的進程;
2,n個競爭資源的進程;
操作:
對於競爭資源的進程來說:1,向數據中心請求鎖;2,獲取鎖,進入臨界區資源執行相應邏輯;3,釋放鎖;
對於數據中心的進程來說:1,記錄鎖,和當前使用的對象;2,主動推送;
線程池+隊列 與 actor區別:
從控制的角度上看:投遞任務到任務隊列,簡單的負載均衡;適用於針對某一個功能的場景下。
而actor多進程開發中是對進程拆分,1.從功能上考慮;2.熱點拆分,比如網關,當到達性能上限時,可以拆分成多個網關協同管理,可以根據邏輯功能做更好的調度,使用更加靈活,受限於CPU和內存。
鎖機制中不同鎖對比:
Spinlock:空轉等待,不會發生進程切換,適用於不做復雜邏輯的場景下;
互斥鎖:mutex,會發生進程切換,
讀寫鎖:讀鎖:讀狀態加鎖,共享鎖,其他線程以讀的模式進行臨界區,不會發生堵塞;寫鎖: 寫狀態加鎖,獨享鎖,其他線程嘗試訪問該臨界區,都會堵塞等待。
主要適用於讀遠大於寫的場景中;
條件變量:條件不滿足的線程會進入睡眠,條件滿足會喚醒;常與互斥鎖配合使用,具體參
這里寫線程池偽代碼,詳細見線程池代碼;
//*****************************************************************
pthread_mutex_lock(&mutex);
while(條件不滿足){
pthread_cont_wait(&cond,&mutex);//釋放mutex,加cond鎖;
}
pthread_mutex_unlock(&mutex);
//*****************************************************************
pthread_mutex_lock(&mutex);
//修改條件變量狀態;
if(條件滿足){
pthread_cont_signal(&cond,&mutex);//喚醒睡眠線程,linux環境下可能會引起虛假喚醒;
}
pthread_mutex_unlock(&mutex);
//*****************************************************************
pthread_cond_wait存在虛假喚醒的現象:linux環境下,pthread_cond_wait(&cond,&mutex);可能會喚醒多個睡眠的線程;和linux實現相關。
三.並發模型:actor,csp
1, actor 是一個並發模型;erlang(進程) ; skynet C語言底層實現+lua實現業務邏輯
從語言層面上抽象出進程的概念,選擇隔離性,弱化統一性;
A,用於並發計算;b,actor是最基本的計算單元;c,基於消息計算(skynet回調函數,消耗milebox中的消息);d,actor之間相互隔離;
skynet以actor為並發實體
2,csp: goroutine協程;go語言實現的;csp是以goroutine為並發實體;
平衡隔離性和統一性;
多進程:隔離性強,統一性差;多進程是以進程為並發實體;
多線程:鎖類型,應用,鎖粒度; 隔離性差,同一性強;多線程是以線程為並發實體;
skynet以actor為並發實體,skynet核心工作是通過消息調度從而實現對actor的調度。
skynet中用到了鎖和線程,每一個消息一個鎖,控制鎖的粒度比較小;
worker工作線程輪詢全局消息隊列,全局消息隊列存儲的是有消息的actor的消息隊列, worker線程從消息隊列中取出消息調用回調函數來消費消息;worker線程的數目和機器的CPU核心數是相等的,因此相當於一個計算密集型的線程池。
定時器線程;
網絡線程;單線程讀,多線程寫。
四. skynet中的actor服務介紹
actor適用場景:
a)用於並行計算
b)actor是最基本的計算單元
c)基於消息計算-->skynet_cb cb回調函數來執行消息
d)actor之間通過消息溝通並且相互隔離-->隔離的實現機制是內存塊+lua虛擬機,消息存儲在消息隊列中。
actor組成:
1,隔離的環境;是一個結構體來實現的,每個actor都有一塊獨一無二的內存塊;
2,回調函數;消費消息隊列中的消息;
3,milebox消息隊列:用來存儲消息;
struct skynet_context { void * instance;//隔離的環境 struct skynet_module * mod; void * cb_ud;//回調攜帶的環境 skynet_cb cb;//回調函數 struct message_queue *queue;//消息隊列 FILE * logfile; uint64_t cpu_cost; // in microsec uint64_t cpu_start; // in microsec char result[32]; uint32_t handle;//actor句柄 int session_id; int ref; int message_count; bool init; bool endless; bool profile; CHECKCALLING_DECL };
actor隔離環境的實現:分配一塊內存,舉例在log服務中的源碼是這樣實現的,
struct logger { FILE * handle; char * filename; int close; }; struct logger *logger_create(void) { struct logger * inst = skynet_malloc(sizeof(*inst)); inst->handle = NULL; inst->close = 0; inst->filename = NULL; return inst; }
log服務的功能就是從消息隊列中取出消息,然后打印出來。
__init()
__create()
__release();
cb回調函數;消費milebox消息隊列中的消息;
1] actor運行以及消息調度[如下圖所示],actor之間通過milebox消息隊列來進行溝通;全局消息隊列存儲的是有消息的actor的消息隊列指針;
2] actor消息隊列存儲的是actor的消息;
Work進程邏輯:
1,取出有消息的actor的消息隊列;
2,取出消息;
3,通過回調函數(消息)執行;
3] 消息的生產和消費;
1)消息的產生;actor之間消息的傳遞;
2)網絡中生產消息;socket從網絡中獲取到消息數據后,將數據轉發到actor進行處理
3)定時器產生的消息;
消息的消費是通過回調函數來實現的。
skynet的啟動:
skynet_start.c
create_thread(&pid[0], thread_monitor, m);//消息過載服務 create_thread(&pid[1], thread_timer, m);//啟動定時器線程 create_thread(&pid[2], thread_socket, m);//啟動一個網絡線程
五. skynet中的lua服務介紹
lua服務的創建源碼如下,
struct snlua { lua_State * L;//lua虛擬機,就是lua服務的額隔離環境 struct skynet_context * ctx;//上下文 size_t mem;//內存統計 size_t mem_report; size_t mem_limit; };
skynet中的lua服務通過service_snlua.c來加載的;
lua隔離環境是lua虛擬機;
__init():根據不同的參數生成不同的lua虛擬機
__create()
Skynet_callback()設置skynet中的lua虛擬機
End: skynet工作原理總結:
skynet工作原理如下圖所示:
work線程:輪詢全局消息隊列,取出消息隊列,然后通過回調函數執行消息隊列中的消息。
worker線程是如何取消息的,
通過權重表對所有的worker線程設置權重,(-1是指取一條消息;0表示取全部的消息;1表示取一半的消息);邏輯有<< >>未操作來實現的,weight控制取消息的數量,控制worker線程的公平調度。
回調函數處理完消息后,根據當前actor 消息隊列mailbox是否還有消息,如果有消息就將actor消息隊列放在全局消息隊列的隊尾,由其他worker線程繼續需處理。在多個worker線程訪問全局消息隊列這里用到的鎖機制是通過自旋鎖+條件變量實現的。
配置work線程的數目和機器的CPU核心數是相等的;
work線程主要任務執行邏輯,不需要切換,所以這里不適用互斥鎖,而使用的是spinlock()
1.work線程從消息隊列中取消息,並通過回調函數調用;
2,當沒有消息的時候,采用條件變量pthread_cond_wait(&cond,&mutex);進入休眠,釋放CPU;
actor之間發送消息,不需要喚醒worker條件變量;因為worker線程取消息,執行當前actor之間發送的消息,當前至少有一個worker線程是處於活動狀態,處理完當前actor消息后接着去繼續處理其他actor消息;因此不需要去喚醒新的worker線程去執行消息。