青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

陳碩的Blog

Muduo 網絡編程示例之三:定時器

陳碩 (giantchen_AT_gmail)

Blog.csdn.net/Solstice

這是《Muduo 網絡編程示例》系列的第三篇文章。

Muduo 全系列文章列表: http://blog.csdn.net/Solstice/category/779646.aspx

 

程序中的時間

程序中對時間的處理是個大問題,我打算單獨寫一篇文章來全面地討論這個問題。文章暫定名《〈程序中的日期與時間〉第二章 計時與定時》,跟《〈程序中的日期與時間〉第一章 日期計算》放到一個系列,這個系列預計會有四篇文章。

在這篇博客里里我先簡要談談與編程直接相關的內容,把更深入的內容留給上面提到的日期與時間專題文章。

在一般的服務端程序設計中,與時間有關的常見任務有:

  1. 獲取當前時間,計算時間間隔;
  2. 時區轉換與日期計算;把紐約當地時間轉換為上海當地時間;2011-02-05 之后第 100 天是幾月幾號星期幾?等等
  3. 定時操作,比如在預定的時間執行一項任務,或者在一段延時之后執行一項任務。

其中第 2 項看起來復雜,其實最簡單。日期計算用 Julian Day Number,時區轉換用 tz database;惟一麻煩一點的是夏令時,但也可以用 tz database 解決。這些操作都是純函數,很容易用一套單元測試來驗證代碼的正確性。需要特別注意的是,用 tzset/localtime_r 來做時區轉換在多線程環境下可能會有問題;對此我的解決辦法是寫一個 TimeZone class,以避免影響全局,將來在日期與時間專題中會講到。以下本文不考慮時區,均為 UTC 時間。

真正麻煩的是第 1 項和第 3 項。一方面,Linux 有一大把令人眼花繚亂的與時間相關的函數和結構體,在程序中該如何選用?另一方面,計算機中的時鐘不是理想的計時器,它可能會漂移或跳變;最后,民用的 UTC 時間與閏秒的關系也讓定時任務變得復雜和微妙。當然,與系統當前時間有關的操作也讓單元測試變得困難。

Linux 時間函數

Linux 的計時函數,用于獲得當前時間:

  • time(2) / time_t (秒)
  • ftime(3) / struct timeb (毫秒)
  • gettimeofday(2) / struct timeval (微秒)
  • clock_gettime(2) / struct timespec (納秒)
  • gmtime / localtime / timegm / mktime / strftime / struct tm (這些與當前時間無關)

定時函數,用于讓程序等待一段時間或安排計劃任務:

  • sleep
  • alarm
  • usleep
  • nanosleep
  • clock_nanosleep
  • getitimer / setitimer
  • timer_create / timer_settime / timer_gettime / timer_delete
  • timerfd_create / timerfd_gettime / timerfd_settime

我的取舍如下:

  • (計時)只使用 gettimeofday 來獲取當前時間。
  • (定時)只使用 timerfd_* 系列函數來處理定時。

gettimeofday 入選原因:(這也是 muduo::Timestamp class 的主要設計考慮)

  1. time 的精度太低,ftime 已被廢棄,clock_gettime 精度最高,但是它系統調用的開銷比 gettimeofday 大。
  2. 在 x86-64 平臺上,gettimeofday 不是系統調用,而是在用戶態實現的(搜 vsyscall),沒有上下文切換和陷入內核的開銷。
  3. gettimeofday 的分辨率 (resolution) 是 1 微秒,足以滿足日常計時的需要。muduo::Timestamp 用一個 int64_t 來表示從 Epoch 到現在的微秒數,其范圍可達上下 30 萬年。

timerfd_* 入選的原因:

  1. sleep / alarm / usleep 在實現時有可能用了信號 SIGALRM,在多線程程序中處理信號是個相當麻煩的事情,應當盡量避免。(近期我會寫一篇博客仔細講講“多線程、RAII、fork() 與信號”)
  2. nanosleep 和 nanosleep 是線程安全的,但是在非阻塞網絡編程中,絕對不能用讓線程掛起的方式來等待一段時間,程序會失去響應。正確的做法是注冊一個時間回調函數。
  3. getitimer 和 timer_create 也是用信號來 deliver 超時,在多線程程序中也會有麻煩。timer_create 可以指定信號的接收方是進程還是線程,算是一個進步,不過在信號處理函數(signal handler)能做的事情實在很受限。
  4. timerfd_create 把時間變成了一個文件描述符,該“文件”在定時器超時的那一刻變得可讀,這樣就能很方便地融入到 select/poll 框架中,用統一的方式來處理 IO 事件和超時事件,這也正是 Reactor 模式的長處。我在一年前發表的《Linux 新增系統調用的啟示》中也談到這個想法,現在我把這個想法在 muduo 網絡庫中實現了。
  5. 傳統的 Reactor 利用 select/poll/epoll 的 timeout 來實現定時功能,但 poll 和 epoll 的定時精度只有毫秒,遠低于 timerfd_settime 的定時精度。

必須要說明,在 Linux 這種非實時多任務操作系統中,在用戶態實現完全精確可控的計時和定時是做不到的,因為當前任務可能會被隨時切換出去,這在 CPU 負載大的時候尤為明顯。但是,我們的程序可以盡量提高時間精度,必要的時候通過控制 CPU 負載來提高時間操作的可靠性,在程序在 99.99% 的時候都是按預期執行的。這或許比換用實時操作系統并重新編寫并測試代碼要經濟一些。

關于時間的精度(accuracy)問題我留到專題博客文章中討論,它與分辨率(resolution)不完全是一回事兒。時間跳變和閏秒的影響與應對也不在此處展開討論了。

Muduo 的定時器接口

Muduo EventLoop 有三個定時器函數:

   1: typedef boost::function<void()> TimerCallback;
   2:  
   3: ///
   4: /// Reactor, at most one per thread.
   5: ///
   6: /// This is an interface class, so don't expose too much details.
   7: class EventLoop : boost::noncopyable
   8: {
   9:  public:
  10:   // ...
  11:  
  12:   // timers
  13:  
  14:   ///
  15:   TimerId runAt(const Timestamp& time, const TimerCallback& cb);
  16:  
  17:   ///
  18:   /// Runs callback after @c delay seconds.
  19:   /// Safe to call from other threads.
  20:   TimerId runAfter(double delay, const TimerCallback& cb);
  21:  
  22:   ///
  23:   /// Runs callback every @c interval seconds.
  24:   /// Safe to call from other threads.
  25:   TimerId runEvery(double interval, const TimerCallback& cb);
  26:  
  27:   /// Cancels the timer.
  28:   /// Safe to call from other threads.
  29:   // void cancel(TimerId timerId);
  30:  
  31:   // ...
  32: };
  • runAt 在指定的時間調用 TimerCallback
  • runAfter 等一段時間調用 TimerCallback
  • runEvery 以固定的間隔反復調用 TimerCallback
  • cancel 取消 timer,目前未實現

回調函數在 EventLoop 對象所在的線程發生,與 onMessage() onConnection() 等網絡事件函數在同一個線程。

Muduo 的 TimerQueue 采用了最簡單的實現(鏈表)來管理定時器,它的效率比不上常見的 binary heap 的做法,如果程序中大量(10 個以上)使用重復觸發的定時器,或許值得考慮改用更高級的實現。我目前還沒有在一個程序里用過這么多定時器,暫時也不打算優化 TimerQueue。

Boost.Asio Timer 示例

Boost.Asio 教程里以 Timer 和 Daytime 為例介紹 asio 的基本使用,daytime 已經在前文“示例一”中介紹過,這里著重談談 Timer。Asio 有 5 個 Timer 示例,muduo 把其中四個重新實現了一遍,并擴充了第 5 個示例。

  1. 阻塞式的定時,muduo 不支持這種用法,無代碼。
  2. 非阻塞定時,見 examples/asio/tutorial/timer2
  3. 在 TimerCallback 里傳遞參數,見 examples/asio/tutorial/timer3
  4. 以成員函數為 TimerCallback,見 examples/asio/tutorial/timer4
  5. 在多線程中回調,用 mutex 保護共享變量,見 examples/asio/tutorial/timer5
  6. 在多線程中回調,縮小臨界區,把不需要互斥執行的代碼移出來,見 examples/asio/tutorial/timer6

為節省篇幅,這里只列出 timer4:

   1: #include <muduo/net/EventLoop.h>
   2:  
   3: #include <iostream>
   4: #include <boost/bind.hpp>
   5: #include <boost/noncopyable.hpp>
   6:  
   7: class Printer : boost::noncopyable
   8: {
   9:  public:
  10:   Printer(muduo::net::EventLoop* loop)
  11:     : loop_(loop),
  12:       count_(0)
  13:   {
  14:     loop_->runAfter(1, boost::bind(&Printer::print, this));
  15:   }
  16:  
  17:   ~Printer()
  18:   {
  19:     std::cout << "Final count is " << count_ << "\n";
  20:   }
  21:  
  22:   void print()
  23:   {
  24:     if (count_ < 5)
  25:     {
  26:       std::cout << count_ << "\n";
  27:       ++count_;
  28:  
  29:       loop_->runAfter(1, boost::bind(&Printer::print, this));
  30:     }
  31:     else
  32:     {
  33:       loop_->quit();
  34:     }
  35:   }
  36:  
  37: private:
  38:   muduo::net::EventLoop* loop_;
  39:   int count_;
  40: };
  41:  
  42: int main()
  43: {
  44:   muduo::net::EventLoop loop;
  45:   Printer printer(&loop);
  46:   loop.loop();
  47: }

最后我再強調一遍,在非阻塞服務端編程中,絕對不能用  sleep 或類似的辦法來讓程序原地停留等待,這會讓程序失去響應,因為主事件循環被掛起了,無法處理 IO 事件。這就像在 Windows 編程中絕對不能在消息循環里執行耗時的代碼一樣,會讓程序界面失去響應。Reactor 模式的網絡編程確實有些類似傳統的消息驅動的 Windows 編程。對于“定時”任務,就把它變成一個特定的消息,到時候觸發相應的消息處理函數就行了。

Boost.Asio 的 timer 示例只用到了 EventLoop::runAfter,我再舉一個 EventLoop::runEvery 的例子。

Java Netty 示例

Netty 是一個非常好的 Java NIO 網絡庫,它附帶的示例程序echodiscard 兩個簡單網絡協議,與前文不同,Netty 版的服務端有流量統計功能,這需要用到 EventLoop::runEvery。

這里列出 discard server 的代碼,其 client 的代碼類似前文的 chargen,為節省篇幅,請閱讀源碼 http://code.google.com/p/muduo/source/browse/trunk/examples/netty/

Discard server 注冊了一個間隔為 3 秒的定時器,調用 DiscardServer::printThroughput 打印出吞吐量。注意這段代碼用了整數的原子操作 AtomicInt64 來記錄收到的字節數和消息數,乍看之下似乎沒有必要,其實 DiscardServer 可以配置成多線程服務器,muduo TcpServer 有一個內置的多線程模型,可以通過 setThreadNum() 來開啟。這個話題留到以后再細說。

   1: #include <muduo/net/TcpServer.h>
   2:  
   3: #include <muduo/base/Atomic.h>
   4: #include <muduo/base/Logging.h>
   5: #include <muduo/base/Thread.h>
   6: #include <muduo/net/EventLoop.h>
   7: #include <muduo/net/InetAddress.h>
   8:  
   9: #include <boost/bind.hpp>
  10:  
  11: #include <utility>
  12:  
  13: #include <stdio.h>
  14: #include <unistd.h>
  15:  
  16: using namespace muduo;
  17: using namespace muduo::net;
  18:  
  19: int numThreads = 0;
  20:  
  21: class DiscardServer
  22: {
  23:  public:
  24:   DiscardServer(EventLoop* loop, const InetAddress& listenAddr)
  25:     : loop_(loop),
  26:       server_(loop, listenAddr, "DiscardServer"),
  27:       oldCounter_(0),
  28:       startTime_(Timestamp::now())
  29:   {
  30:     server_.setConnectionCallback(
  31:         boost::bind(&DiscardServer::onConnection, this, _1));
  32:     server_.setMessageCallback(
  33:         boost::bind(&DiscardServer::onMessage, this, _1, _2, _3));
  34:     server_.setThreadNum(numThreads);
  35:     loop->runEvery(3.0, boost::bind(&DiscardServer::printThroughput, this));
  36:   }
  37:  
  38:   void start()
  39:   {
  40:     LOG_INFO << "starting " << numThreads << " threads.";
  41:     server_.start();
  42:   }
  43:  
  44:  private:
  45:   void onConnection(const TcpConnectionPtr& conn)
  46:   {
  47:     LOG_TRACE << conn->peerAddress().toHostPort() << " -> "
  48:         << conn->localAddress().toHostPort() << " is "
  49:         << (conn->connected() ? "UP" : "DOWN");
  50:   }
  51:  
  52:   void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp)
  53:   {
  54:     size_t len = buf->readableBytes();
  55:     transferred_.add(len);
  56:     receivedMessages_.incrementAndGet();
  57:     buf->retrieveAll();
  58:   }
  59:  
  60:   void printThroughput()
  61:   {
  62:     Timestamp endTime = Timestamp::now();
  63:     int64_t newCounter = transferred_.get();
  64:     int64_t bytes = newCounter - oldCounter_;
  65:     int64_t msgs = receivedMessages_.getAndSet(0);
  66:     double time = timeDifference(endTime, startTime_);
  67:     printf("%4.3f MiB/s %4.3f Ki Msgs/s %6.2f bytes per msg\n",
  68:         static_cast<double>(bytes)/time/1024/1024,
  69:         static_cast<double>(msgs)/time/1024,
  70:         static_cast<double>(bytes)/static_cast<double>(msgs));
  71:  
  72:     oldCounter_ = newCounter;
  73:     startTime_ = endTime;
  74:   }
  75:  
  76:   EventLoop* loop_;
  77:   TcpServer server_;
  78:  
  79:   AtomicInt64 transferred_;
  80:   AtomicInt64 receivedMessages_;
  81:   int64_t oldCounter_;
  82:   Timestamp startTime_;
  83: };
  84:  
  85: int main(int argc, char* argv[])
  86: {
  87:   LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid();
  88:   if (argc > 1)
  89:   {
  90:     numThreads = atoi(argv[1]);
  91:   }
  92:   EventLoop loop;
  93:   InetAddress listenAddr(2009);
  94:   DiscardServer server(&loop, listenAddr);
  95:  
  96:   server.start();
  97:  
  98:   loop.loop();
  99: }

運行方法,在同一臺機器的兩個命令行窗口分別運行:

$ bin/netty_discard_server

$ bin/netty_discard_client 127.0.0.1 256

第一個窗口顯示吞吐量:

41.001 MiB/s 73.387 Ki Msgs/s 572.10 bytes per msg
72.441 MiB/s 129.593 Ki Msgs/s 572.40 bytes per msg
77.724 MiB/s 137.251 Ki Msgs/s 579.88 bytes per msg

改變第二個命令的最后一個參數(上面的 256),可以觀察不同的消息大小對吞吐量的影響。

練習 1:把二者的關系繪制成函數曲線,看看有什么規律,想想為什么。

練習 2:在局域網的兩臺機器上運行客戶端和服務端,找出讓吞吐量達到最大的消息長度。這個數字與練習 1 中的相比是大還是小?為什么?

有興趣的讀者可以對比一下 Netty 的吞吐量,muduo 應該能輕松取勝。

discard client/server 測試的是單向吞吐量,echo client/server 測試的是雙向吞吐量。這兩個服務端都支持多個并發連接,兩個客戶端都是單連接的。本系列第 6 篇文章將會實現一個 pingpong 協議,用來測試 muduo 在多線程大量連接情況下的表現。

(待續)

posted on 2011-02-06 22:56 陳碩 閱讀(7653) 評論(3)  編輯 收藏 引用 所屬分類: muduo

評論

# re: Muduo 網絡編程示例之三:定時器 2014-08-21 14:49 ian

我的系統是CentOS 6.5;安裝了muduo-1.0.3。直接運行../build/release/bin/asio_tutorial_timer2、netty_discard_server等關于定時器的程序會Illegal instruction(core dumped);而其他程序不行,請問一般情況下是什么原因呢?  回復  更多評論   

# re: Muduo 網絡編程示例之三:定時器 2014-08-21 15:00 陳碩

@ian
把 CMakeLists.txt 中 -march=native 這句話去掉,重新編譯試試。  回復  更多評論   

# re: Muduo 網絡編程示例之三:定時器 2014-08-21 16:34 ian

@陳碩
謝謝陳老師這么快就回答了,去掉-march=native后重新編譯正常了。
  回復  更多評論   

<2025年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

導航

統計

常用鏈接

隨筆分類

隨筆檔案

相冊

搜索

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            亚洲午夜免费福利视频| 欧美在线黄色| 国产精品免费视频观看| 欧美精品情趣视频| 欧美日本韩国| 欧美午夜片在线观看| 国产精品入口麻豆原神| 国一区二区在线观看| 原创国产精品91| 日韩一区二区免费高清| 亚洲婷婷免费| 久久综合网络一区二区| 欧美岛国在线观看| 一区二区三区国产盗摄| 久久精品夜色噜噜亚洲a∨ | 免费在线一区二区| 亚洲欧洲一区二区在线观看| 亚洲毛片在线观看| 欧美一二三区在线观看| 欧美大片免费| 国产日韩欧美在线看| 亚洲日韩第九十九页| 欧美一级二级三级蜜桃| 欧美激情精品久久久久久| 亚洲一二三区在线| 久久久久久久一区二区| 国产精品福利网| 亚洲成色999久久网站| 亚洲欧美bt| 亚洲欧洲日本国产| 久久久久青草大香线综合精品| 欧美激情1区2区| 国产视频久久网| 亚洲午夜精品一区二区三区他趣| 久久亚洲午夜电影| 亚洲视频观看| 欧美福利网址| 亚洲视频一区二区在线观看 | 欧美男人的天堂| 国产日韩欧美不卡在线| 99精品福利视频| 欧美国产高潮xxxx1819| 性感少妇一区| 国产精品久久久久久久久免费| 亚洲黄一区二区三区| 欧美主播一区二区三区| 日韩一区二区精品葵司在线| 久久综合狠狠| 韩曰欧美视频免费观看| 午夜在线电影亚洲一区| 一本一道久久综合狠狠老精东影业| 免费亚洲婷婷| 狠狠色综合色区| 美国三级日本三级久久99| 亚洲欧美国产日韩天堂区| 国产精品外国| 欧美在线啊v一区| 欧美制服丝袜| 国产在线视频欧美| 久久人人爽爽爽人久久久| 欧美一区91| 韩国免费一区| 欧美成人精品不卡视频在线观看| 久久精品日韩| 亚洲福利视频在线| 亚洲二区视频| 欧美人与性动交α欧美精品济南到 | 香蕉亚洲视频| 亚洲图片欧美日产| 国产精品久久久久久久久动漫 | 欧美成人午夜激情视频| 久久中文字幕导航| 亚洲精品欧美极品| 亚洲蜜桃精久久久久久久| 欧美欧美全黄| 午夜精品一区二区在线观看| 亚洲欧美综合一区| 一区二区三区中文在线观看| 欧美不卡视频一区发布| 免费精品99久久国产综合精品| 亚洲精选91| 亚洲一区二区伦理| 一区二区三区自拍| 亚洲人午夜精品| 国产精品黄色在线观看| 久久狠狠婷婷| 欧美国产精品中文字幕| 亚洲伊人观看| 久久精品亚洲一区| 夜夜嗨av一区二区三区四季av| 9国产精品视频| 国产真实乱偷精品视频免| 农夫在线精品视频免费观看| 亚洲视频大全| 在线观看一区欧美| 亚洲欧洲一区二区三区久久| 国产精品区一区| 亚洲第一久久影院| 国产精品三区www17con| 欧美chengren| 国产午夜亚洲精品不卡| 亚洲欧洲午夜| 精品动漫3d一区二区三区| 日韩系列欧美系列| 狠狠色伊人亚洲综合网站色| 日韩视频中文| 在线观看av不卡| 亚洲一区二区三区久久 | 久久疯狂做爰流白浆xx| 欧美sm视频| 欧美一区二区三区另类| 欧美激情视频网站| 蜜臀91精品一区二区三区| 欧美日韩一级片在线观看| 久久综合色播五月| 欧美性片在线观看| 亚洲精品视频一区| 狠狠色丁香婷婷综合| 亚洲小少妇裸体bbw| 日韩亚洲欧美在线观看| 久久亚洲风情| 免费91麻豆精品国产自产在线观看| 国产精品白丝av嫩草影院| 欧美成人一区在线| 国语自产偷拍精品视频偷| 亚洲午夜久久久久久久久电影院| 99re国产精品| 免费日韩成人| 欧美国产精品中文字幕| 亚洲国产综合在线| 蜜臀99久久精品久久久久久软件| 久久琪琪电影院| 国产综合香蕉五月婷在线| 亚洲免费在线电影| 午夜亚洲伦理| 国产精品一区免费在线观看| 亚洲色图制服丝袜| 亚洲欧美激情四射在线日| 欧美日韩一区精品| 在线视频欧美一区| 亚洲欧美偷拍卡通变态| 欧美日韩在线播放一区二区| 日韩午夜av| 午夜一区二区三区在线观看 | 国产精品美女久久久久久免费| 一区二区高清视频| 亚洲新中文字幕| 欧美午夜精品久久久久久人妖| 亚洲麻豆av| 亚洲欧美激情视频| 国产午夜精品久久久久久久| 欧美在线看片| 欧美va亚洲va日韩∨a综合色| 在线观看亚洲专区| 欧美激情精品久久久| 日韩视频在线观看一区二区| 亚洲欧美成人一区二区三区| 国产精品亚洲视频| 午夜视频在线观看一区二区| 亚洲国产女人aaa毛片在线| 久久另类ts人妖一区二区| 欧美成人免费大片| 一区二区三区高清在线观看| 欧美日韩免费一区二区三区| 亚洲一区三区视频在线观看| 久久久久久久一区二区三区| 在线观看亚洲| 欧美日韩精品一区二区| 亚洲一二三区在线| 欧美88av| 欧美一区二区私人影院日本| 亚洲国产一区二区三区青草影视| 欧美日韩在线视频一区| 香蕉久久夜色| 亚洲精品中文字幕女同| 久久乐国产精品| 在线一区二区三区做爰视频网站| 国产偷自视频区视频一区二区| 免费亚洲一区| 欧美一区二区三区免费视| 亚洲激情视频在线| 久久久99爱| 亚洲午夜在线| 亚洲激情第一区| 国产欧美精品一区二区三区介绍| 免费不卡欧美自拍视频| 亚洲欧美日本另类| 亚洲理论电影网| 欧美黄色一区二区| 久久精品国产成人| 亚洲综合成人在线| 亚洲日本电影在线| 狠狠干综合网| 国产一区二区三区日韩| 欧美午夜精品| 欧美成黄导航| 久久亚洲风情| 久久久久久久尹人综合网亚洲| 亚洲欧美日韩视频二区| 亚洲另类自拍|