從今天開始記錄自己的學習記錄,不然渾渾噩噩,感覺很多東西都沒記住
2019.07.16
1.ec:一個把libevent2封裝了的C++庫。
2.evpp:
360的一個高性能的開發開源庫,google代碼規範的風格,基於libevent2, boost, gtest,glog庫開發,值得好好學習,今晚還沒編譯完成,等編譯完成好好研究一下源碼。
3.boost::asio:
性能非常高的異步IO庫,據說比libevent的性能高,好好研究怎麼用,看一下能不能解決週末自己用epoll + 線程池寫的服務器出現io問題。
4.epoll和select:來自網絡
- select:
select本質上是通過設置或者檢查存放fd標誌位的數據結構來進行下一步處理。這樣所帶來的缺點是:
1、 單個進程可監視的fd數量被限制,即能監聽端口的大小有限。
一般來說這個數目和系統內存關係很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.
2、 對socket進行掃描時是線性掃描,即採用輪詢的方法,效率較低:
當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字註冊某個回調函數,當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。
3、需要維護一個用來存放大量fd的數據結構,這樣會使得用戶空間和內核空間在傳遞該結構時複製開銷大
- epoll
epoll有EPOLLLT和EPOLLET兩種觸發模式,LT是默認的模式,ET是“高速”模式。LT模式下,只要這個fd還有數據可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發)模式中,它只會提示一次,直到下次再有數據流入之前都不會再提示了,無 論fd中是否還有數據可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小於請求值,或者 遇到EAGAIN錯誤。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,內核就會採用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。
epoll爲什麼要有EPOLLET觸發模式?
如果採用EPOLLLT模式的話,系統中一旦有大量你不需要讀寫的就緒文件描述符,它們每次調用epoll_wait都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率.。而採用EPOLLET這種邊沿觸發模式的話,當被監控的文件描述符上有可讀寫事件發生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數據全部讀寫完(如讀寫緩衝區太小),那麼下次調用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現第二次可讀寫事件纔會通知你!!!這種模式比水平觸發效率高,系統不會充斥大量你不關心的就緒文件描述符
epoll的優點:
1、沒有最大併發連接的限制,能打開的FD的上限遠大於1024(1G的內存上能監聽約10萬個端口);
2、效率提升,不是輪詢的方式,不會隨着FD數目的增加效率下降。只有活躍可用的FD纔會調用callback函數;
即Epoll最大的優點就在於它只管你“活躍”的連接,而跟連接總數無關,因此在實際的網絡環境中,Epoll的效率就會遠遠高於select和poll。3、 內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少複製開銷。
-
可參考:https://www.open-open.com/lib/view/open1410403215664.html
5.零零碎碎
C++11特性:
1.std::bind();
template< class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
template< class R, class F, class... Args >
/*unspecified*/ bind( F&& f, Args&&... args );
f:一個可調用對象(可以是函數對象、函數指針、函數引用、成員函數指針、數據成員指針),它的參數將被綁定到args上。
args:綁定參數列表,參數會被值或佔位符替換,其長度必須與f接收的參數個數一致
調用std::bind的一般形式爲:
auto newCallable = std::bind(callable, arg_list);
其中,newCallable本身是一個可調用對象,arg_list是一個逗號分隔的參數列表,對應給定的callable的參數。即,當我們調用newCallable時,newCallable會調用callable,並傳遞給它arg_list中的參數。
2.std::function<函數指針>:
來自#include <functional>;
//一個函數指針的模板容器
int add(int a, int b);
std::function<int(int,int)> f = add;
typedef int (*ADD)(int a, int b); //等價於
typedef std::function<int(int, int)> ADD; //感覺可讀性較高,更加好理解
2019.07.17
1.evpp:
成功編譯,在編譯過程中出現兩個問題:
/evpp/benchmark/throughput/asio_from_chenshuo/handler_allocator.hpp:23:1: 錯誤:expected class-name before ‘{’ token
解決:在evpp/benchmark/throughput/asio_from_chenshuo/handler_allocator.hpp,添加頭文件<boost/utility.hpp>
對‘vtable for boost::detail::thread_data_base’未定義的引用。
解決:修改evpp/build/benchmark/throughput/asio_from_chenshuo/CMakeFiles/benchmark_tcp_asio_server.dir/link.txt,添加動態鏈接庫-lboost_thread;
2.std::enable_shared_from_this:
如果一個T類型的對象t,是被std::shared_ptr管理的,且類型T繼承自std::enable_shared_from_this,那麼T就有個shared_from_this的成員函數,這個函數返回一個新的std::shared_ptr的對象,也指向對象t。相當於兩個std::shared_ptr共同管理一個對象,並且共同維護該對象的計數器。
//初始化
std::shared_ptr<int> s_ptr = make_shared<int>(1); //推薦的安全的寫法
//或者
int *i_ptr = new int(1);
std::shared_ptr s_ptr(i_ptr);//則有可能會意外使用了i_ptr,導致s_ptr可能失控i_ptr易出錯,不推薦。
std::shared_ptr<int> s_ptr = make_shared<int>(new int(1)); //錯誤寫法,因爲make_shared會調用類型T的構造函數,make_shared調用int*的構造函數,錯誤。
std::shared_ptr<int> s_ptr = i_ptr;//錯誤寫法,因爲s_ptr是類對象,i_ptr是指針。
2019.07.22
evpp學習:上週編譯成功後,開始學習evpp的代碼,都是回調函數以及C++11的特性,看起來還是有點喫力,不過這幾天看了一下已經有點清楚了裏面關於TCPServer的整個框架,自己根據example裏面的TCPServer例子,寫了小測試,從客戶端連接->發送數據debug跟蹤了一下,大概摸清楚了流程。畫了類圖,今天先上圖記錄,後續會繼續補充。
先用文字記錄一下,後續找一下如何用流程圖來表達,因爲好多的回調函數,所以還沒想好如何表達可以更加的清楚。
1.TCPServer.cc:
EventLoop* loop_(傳遞主線程的EventLoop,用於監聽處理連接請求);
std::shared_ptr<EventLoopThreadPool> tpool_(線程池,每個線程都有一個EventLoop對象,用戶處理客戶端的連接的fd 讀/寫);
std::map<uint64_t, TCPConnPtr> connections_(管理當前連接的所有客戶端);
這三個成員比較重要。
2019.07.25
1.柔性數組:在結構體的最後一個元素是一個大小未知的數組,就是0長度,int array[];主要是用於變長度,解決使用數組時內存的冗餘和數組越界。
struct SoftArray{
int len; // data的長度
int data[];// data的內存
};
sizeof(SoftArray) = 4; //對編譯器而言,數組名只是一個符號,不會佔用任何空間,只是一個偏移量
int len = 10;
SoftArray *p = (SoftArray*)malloc(sizeof(SoftArray) + sizeof(int)*len);
p->len = len; // p->data指向的是4 * 10字節的內存空間。
2019.07.30
1.缺頁中斷:“進程線性地址空間裏的頁面不必常駐內存,在執行一條指令時,如果發現他要訪問的頁沒有在內存中(即存在位爲0),那麼停止該指令的執行,併產生一個頁不存在的異常,對應的故障處理程序可通過從外存加載該頁的方法來排除故障,之後,原先引起的異常的指令就可以繼續執行,而不再產生異常。”
前段時間不能夠很好地理解,缺頁中斷是一個怎樣過程和原因,今天看到了一篇關於虛擬內存,總算是上下文結合起來了,有了個具體的概念。主要是外存和內存,外存是硬盤的一部分作爲內存使用,因爲程序只有一部分加載到內存中,其餘部分存放到外存中。如果程序執行到某條指令時指向虛擬內存地址沒有在當前的內存頁中找到,則發生缺頁中斷,通過頁面調度算法進行內存頁置換,將外存中的頁面加入到內存中,使程序進行運行。
置換算法:FIFO、LRU、OPT
C++實現,模擬LRU:
class LRUCache {
public:
LRUCache(int capacity) {
capacity_ = capacity;
}
int get(int key) {
if(cache_map_.find(key) == cache_map_.end())
return -1;
//將cache_list_中的cache_map_[key]指向的迭代器,插入到cache_list_.begin()的前一個位置
cache_list_.splice(cache_list_.begin(), cache_list_, cache_map_[key]);
cache_map_[key] = cache_list_.begin();
return cache_map_[key]->val_;
}
void put(int key, int value) {
if(cache_map_.find(key) != cache_map_.end()) {
cache_map_[key]->val_= value;
cache_list_.splice(cache_list_.begin(), cache_list_, cache_map_[key]);
cache_map_[key] = cache_list_.begin();
} else {
if(cache_list_.size() >= capacity_){
cache_map_.erase(cache_list_.back().key_);
cache_list_.pop_back();
}
cache_list_.push_front(Node(key, value));
cache_map_[key] = cache_list_.begin();
}
}
private:
struct Node {
int key_;
int val_;
Node(int key, int val):key_(key), val_(val){}
};
int capacity_;
list<Node> cache_list_;
unordered_map<int, list<Node>::iterator> cache_map_;
};
2.字符串匹配
KMP算法:https://www.cnblogs.com/ZuoAndFutureGirl/p/9028287.html
3.C++多態
運行時多態:通過繼承和虛函數。
編譯時多態:函數重載和運算符重載。
4.重載
重載是同一個類中函數名一樣,參數列表不一樣的函數,編譯時,編譯器加以區別,所以在編譯時已經綁定了地址,是靜態綁定,所以和所說的運行時多態無關。而重寫是virtual函數,在編譯時是不確定的,因爲new是在運行時才調用構造函數,才能確定虛函數表所以是動態綁定的。