muduo網路庫學習之EventLoop(四):EventLoopThread 類、EventLoopThreadPool 類
1、EventLoopThread(IO執行緒類)
IO執行緒不一定是主執行緒
muduo併發模型one loop per thread + threadpool(計算執行緒池)
為了方便今後使用,定義了EventLoopThread類,該類封裝了IO執行緒
EventLoopThread建立了一個執行緒線上程函式中建立了一個EvenLoop物件並呼叫EventLoop::loop
多個IO執行緒可以用IO執行緒池來管理,對應的類是EventLoopThreadPool
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
class EventLoopThread : boost::noncopyable
{ public: typedef boost::function<void(EventLoop *)> ThreadInitCallback; EventLoopThread(const ThreadInitCallback &cb = ThreadInitCallback()); ~EventLoopThread(); EventLoop *startLoop()// 啟動執行緒,該執行緒就成為了IO執行緒 { thread_.start(); // 執行threadFunc(); 建構函式初始化列表中thread_(boost::bind(&EventLoopThread::threadFunc, this)) .... }; private: void threadFunc(); // 執行緒函式 EventLoop *loop_; // loop_指標指向一個EventLoop物件 bool exiting_; Thread thread_; MutexLock mutex_; Condition cond_; ThreadInitCallback callback_; // 回撥函式在EventLoop::loop事件迴圈之前被呼叫 }; EventLoopThread::~EventLoopThread() { exiting_ = true; loop_->quit(); // 退出IO執行緒,讓IO執行緒的loop迴圈退出,從而退出了IO執行緒 thread_.join(); }
C++ Code
{ EventLoop loop; if (callback_) { callback_(&loop); } { MutexLockGuard lock(mutex_); // 一般情況是EventLoopThread物件先析構,解構函式呼叫loop_->quit() 使得loop.loop() 退出迴圈 // 這樣threadFunc 退出,loop棧上物件析構,loop_ 指標失效,但此時已經不會再通過loop_ 訪問loop, // 故不會有問題。 loop_ = &loop; cond_.notify(); } loop.loop(); //assert(exiting_); } |
測試程式碼:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#include <muduo/net/EventLoop.h>
#include <muduo/net/EventLoopThread.h> #include <stdio.h> using namespace muduo; using namespace muduo::net; void runInThread() { printf("runInThread(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); } int main() { printf("main(): pid = %d, tid = %d\n", getpid(), CurrentThread::tid()); EventLoopThread loopThread; EventLoop *loop = loopThread.startLoop(); // 非同步呼叫runInThread,即將runInThread新增到loop物件所在IO執行緒,讓該IO執行緒執行 loop->runInLoop(runInThread); sleep(1); // runAfter內部也呼叫了runInLoop,所以這裡也是非同步呼叫,讓該IO執行緒新增一個2s定時器 loop->runAfter(2, runInThread); sleep(3); //~EventLoopThread()會呼叫loop_->quit(); printf("exit main().\n"); } |
執行輸出如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./reactor_test06
20131108 03:29:12.749530Z 2628 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 2628, tid = 2628
20131108 03:29:12.753135Z 2629 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 03:29:12.753794Z 2629 TRACE EventLoop EventLoop created 0xB7415F44 in thread 2629 - EventLoop.cc:76
20131108 03:29:12.754266Z 2629 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 03:29:12.754707Z 2629 TRACE loop EventLoop 0xB7415F44 start looping - EventLoop.cc:108
20131108 03:29:12.755088Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:12.756033Z 2629 TRACE printActiveChannels {5: IN } - EventLoop.cc:271
runInThread(): pid = 2628, tid = 2629
20131108 03:29:13.755730Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:13.756388Z 2629 TRACE printActiveChannels {5: IN } - EventLoop.cc:271
20131108 03:29:15.755858Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:15.757316Z 2629 TRACE printActiveChannels {4: IN } - EventLoop.cc:271
20131108 03:29:15.757469Z 2629 TRACE readTimerfd TimerQueue::handleRead() 1 at 1383881355.757345 - TimerQueue.cc:62
runInThread(): pid = 2628, tid = 2629
exit main().
20131108 03:29:16.755942Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:16.755988Z 2629 TRACE printActiveChannels {5: IN } - EventLoop.cc:271
20131108 03:29:16.756003Z 2629 TRACE loop EventLoop 0xB7415F44 stop looping - EventLoop.cc:133
simba@ubuntu:~/Documents/build/debug/bin$
20131108 03:29:12.749530Z 2628 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 2628, tid = 2628
20131108 03:29:12.753135Z 2629 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 03:29:12.753794Z 2629 TRACE EventLoop EventLoop created 0xB7415F44 in thread 2629 - EventLoop.cc:76
20131108 03:29:12.754266Z 2629 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 03:29:12.754707Z 2629 TRACE loop EventLoop 0xB7415F44 start looping - EventLoop.cc:108
20131108 03:29:12.755088Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:12.756033Z 2629 TRACE printActiveChannels {5: IN } - EventLoop.cc:271
runInThread(): pid = 2628, tid = 2629
20131108 03:29:13.755730Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:13.756388Z 2629 TRACE printActiveChannels {5: IN } - EventLoop.cc:271
20131108 03:29:15.755858Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:15.757316Z 2629 TRACE printActiveChannels {4: IN } - EventLoop.cc:271
20131108 03:29:15.757469Z 2629 TRACE readTimerfd TimerQueue::handleRead() 1 at 1383881355.757345 - TimerQueue.cc:62
runInThread(): pid = 2628, tid = 2629
exit main().
20131108 03:29:16.755942Z 2629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 03:29:16.755988Z 2629 TRACE printActiveChannels {5: IN } - EventLoop.cc:271
20131108 03:29:16.756003Z 2629 TRACE loop EventLoop 0xB7415F44 stop looping - EventLoop.cc:133
simba@ubuntu:~/Documents/build/debug/bin$
主執行緒不是IO執行緒,根據前面的文章,timerfd_ = 4, wakeupFd_ = 5。主執行緒呼叫 loop->runInLoop(runInThread); 由於不是IO線
程呼叫runInLoop, 故呼叫queueInLoop() 將runInThead 新增到佇列,然後wakeup() IO執行緒,IO執行緒在doPendingFunctors()
中取
出佇列的runInThread()執行,可以看到IO執行緒的tid 跟主執行緒不一樣。同理,loop->runAfter(2, runInThread);
也是一樣的流程,需
要喚醒一下,此時只是執行runAfter() 新增了一個2s的定時器,
2s超時,timerfd_ 可讀,先handleRead()一下然後執行回撥函式
runInThread()。那為什麼exit main()
之後wakeupFd_ 還會有可讀事件呢?那是因為EventLoopThead 棧上物件析構,在解構函式內
loop_ ->quit(), 由於不是在IO執行緒呼叫quit(),故也需要喚醒一下,IO執行緒才能從poll 返回,這樣再次迴圈判斷 while (!quit_)
就能
退出IO執行緒。
2、EventLoopThreadPool(IO執行緒池類)
![](https://i.iter01.com/images/6d789e86a6751c97af1eda6468e444208f41e72d41e4957e0c24f9dad80da83c.png)
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
class EventLoopThreadPool : boost::noncopyable
{ public: typedef boost::function<void(EventLoop *)> ThreadInitCallback; EventLoopThreadPool(EventLoop *baseLoop); ~EventLoopThreadPool(); void setThreadNum(int numThreads) { numThreads_ = numThreads; } void start(const ThreadInitCallback &cb = ThreadInitCallback()); // 如果loops_為空,則loop指向baseLoop_ // 如果不為空,按照round-robin(RR,輪叫)的排程方式選擇一個EventLoop EventLoop *getNextLoop(); private: EventLoop *baseLoop_; // 與Acceptor所屬EventLoop相同 bool started_; int numThreads_; // 執行緒數,除去mainReactor int next_; // 新連線到來,所選擇的EventLoop物件下標 boost::ptr_vector<EventLoopThread> threads_; // IO執行緒列表 std::vector<EventLoop *> loops_; // EventLoop列表 }; void EventLoopThreadPool::start(const ThreadInitCallback &cb) { assert(!started_); baseLoop_->assertInLoopThread(); started_ = true; for (int i = 0; i < numThreads_; ++i) { EventLoopThread *t = new EventLoopThread(cb); threads_.push_back(t); loops_.push_back(t->startLoop()); // 啟動EventLoopThread執行緒,在進入事件迴圈之前,會呼叫cb } if (numThreads_ == 0 && cb) { // 只有一個EventLoop,在這個EventLoop進入事件迴圈之前,呼叫cb cb(baseLoop_); } } |
現在使用 mainReactor + ThreadPool(subReactors) 模式,則baseLoop_ 與TcpServer 和 Acceptor 中的 loop_ 成員是相同的,即mainReactor 處理監聽事件,已連線套接字事件輪詢給執行緒池中的subReactors 處理。此時需要注意,建立一個TcpConnection物件時,需要繫結其中一個subReactor, 如下:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void TcpServer::newConnection(int sockfd, const InetAddress &peerAddr)
{ loop_->assertInLoopThread(); // 按照輪叫的方式選擇一個EventLoop EventLoop *ioLoop = threadPool_->getNextLoop(); InetAddress localAddr(sockets::getLocalAddr(sockfd)); TcpConnectionPtr conn(new TcpConnection(ioLoop, connName, sockfd, localAddr, peerAddr)); connections_[connName] = conn; ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn)); } |
測試程式:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h> #include <muduo/net/InetAddress.h> #include <boost/bind.hpp> #include <stdio.h> using namespace muduo; using namespace muduo::net; class TestServer { public: TestServer(EventLoop *loop, const InetAddress &listenAddr, int numThreads) : loop_(loop), server_(loop, listenAddr, "TestServer"), numThreads_(numThreads) { server_.setConnectionCallback( boost::bind(&TestServer::onConnection, this, _1)); server_.setMessageCallback( boost::bind(&TestServer::onMessage, this, _1, _2, _3)); server_.setThreadNum(numThreads); } void start() { server_.start(); } private: void onConnection(const TcpConnectionPtr &conn) { if (conn->connected()) { printf("onConnection(): new connection [%s] from %s\n", conn->name().c_str(), conn->peerAddress().toIpPort().c_str()); } else { printf("onConnection(): connection [%s] is down\n", conn->name().c_str()); } } void onMessage(const TcpConnectionPtr &conn, const char *data, ssize_t len) { printf("onMessage(): received %zd bytes from connection [%s]\n", len, conn->name().c_str()); } EventLoop *loop_; TcpServer server_; int numThreads_; }; int main() { printf("main(): pid = %d\n", getpid()); InetAddress listenAddr(8888); EventLoop loop; TestServer server(&loop, listenAddr, 4); server.start(); loop.loop(); } |
此時共有5個IO執行緒,1個主執行緒(mainReactor)和 4個執行緒池中的執行緒(subReactor),server.start() 會啟動執行緒池中的4個執行緒,並且啟動mainReactor 監聽:
C++ Code
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 該函式多次呼叫是無害的
// 該函式可以跨執行緒呼叫 void TcpServer::start() { if (!started_) { started_ = true; threadPool_->start(threadInitCallback_); } if (!acceptor_->listenning()) { // get_pointer返回原生指標 loop_->runInLoop( boost::bind(&Acceptor::listen, get_pointer(acceptor_))); } } |
開啟兩個telnet 客戶端連線伺服器,其中一個輸入aaaa, 另一個輸入ddddd, 伺服器端輸出如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./reactor_test10
main(): pid = 8628
20131108 11:33:15.190620Z 8628 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 11:33:15.191246Z 8628 TRACE EventLoop EventLoop created 0xBFB77D50 in thread 8628 - EventLoop.cc:62
20131108 11:33:15.191568Z 8628 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
main(): pid = 8628
20131108 11:33:15.190620Z 8628 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131108 11:33:15.191246Z 8628 TRACE EventLoop EventLoop created 0xBFB77D50 in thread 8628 - EventLoop.cc:62
20131108 11:33:15.191568Z 8628 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131108 11:33:15.192270Z 8629 TRACE updateChannel fd = 9 events = 3 - EPollPoller.cc:104
20131108 11:33:15.192625Z 8629 TRACE EventLoop EventLoop created 0xB7484F44 in thread 8629 - EventLoop.cc:62
20131108 11:33:15.192927Z 8629 TRACE updateChannel fd = 10 events = 3 - EPollPoller.cc:104
20131108 11:33:15.193356Z 8629 TRACE loop EventLoop 0xB7484F44 start looping - EventLoop.cc:94
20131108 11:33:15.193759Z 8630 TRACE updateChannel fd = 12 events = 3 - EPollPoller.cc:104
20131108 11:33:15.194100Z 8630 TRACE EventLoop EventLoop created 0xB6AFEF44 in thread 8630 - EventLoop.cc:62
20131108 11:33:15.194398Z 8630 TRACE updateChannel fd = 13 events = 3 - EPollPoller.cc:104
20131108 11:33:15.194786Z 8630 TRACE loop EventLoop 0xB6AFEF44 start looping - EventLoop.cc:94
20131108 11:33:15.195135Z 8631 TRACE updateChannel fd = 15 events = 3 - EPollPoller.cc:104
20131108 11:33:15.195534Z 8631 TRACE EventLoop EventLoop created 0xB60FEF44 in thread 8631 - EventLoop.cc:62
20131108 11:33:15.207467Z 8631 TRACE updateChannel fd = 16 events = 3 - EPollPoller.cc:104
20131108 11:33:15.208169Z 8631 TRACE loop EventLoop 0xB60FEF44 start looping - EventLoop.cc:94
20131108 11:33:15.208940Z 8632 TRACE updateChannel fd = 18 events = 3 - EPollPoller.cc:104
20131108 11:33:15.209576Z 8632 TRACE EventLoop EventLoop created 0xB58FDF44 in thread 8632 - EventLoop.cc:62
20131108 11:33:15.210087Z 8632 TRACE updateChannel fd = 19 events = 3 - EPollPoller.cc:104
20131108 11:33:15.210445Z 8628 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20131108 11:33:15.210750Z 8628 TRACE loop EventLoop 0xBFB77D50 start looping - EventLoop.cc:94
20131108 11:33:15.211122Z 8632 TRACE loop EventLoop 0xB58FDF44 start looping - EventLoop.cc:94
20131108 11:33:18.958878Z 8628 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 11:33:18.959167Z 8628 TRACE printActiveChannels {6: IN} - EventLoop.cc:257
20131108 11:33:18.959226Z 8628 INFO TcpServer::newConnection [TestServer] - new connection[TestServer:0.0.0.0:8888#1] from 127.0.0.1:56411 - TcpServer.cc:93
20131108 11:33:18.959262Z 8628 DEBUG TcpConnection TcpConnection::ctor[TestServer:0.0.0.0:8888#1] at 0x8C84F98 fd=20 - TcpConnection.cc:62
20131108 11:33:18.959277Z 8628 TRACE newConnection [1] usecount=1 - TcpServer.cc:111
20131108 11:33:18.959300Z 8628 TRACE newConnection [2] usecount=2 - TcpServer.cc:113
20131108 11:33:18.959322Z 8628 TRACE newConnection [5] usecount=3 - TcpServer.cc:122
20131108 11:33:18.959343Z 8629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 11:33:18.959365Z 8629 TRACE printActiveChannels {10: IN } - EventLoop.cc:257
20131108 11:33:18.959378Z 8629 TRACE connectEstablished [3] usecount=3 - TcpConnection.cc:78
20131108 11:33:18.959409Z 8629 TRACE updateChannel fd = 20 events = 3 - EPollPoller.cc:104
onConnection(): new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:56411
20131108 11:33:18.959433Z 8629 TRACE connectEstablished [4] usecount=3 - TcpConnection.cc:83
20131108 11:33:23.111546Z 8628 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 11:33:23.111628Z 8628 TRACE printActiveChannels {6: IN } - EventLoop.cc:257
20131108 11:33:23.111662Z 8628 INFO TcpServer::newConnection [TestServer] - new connection[TestServer:0.0.0.0:8888#2] from 127.0.0.1:56412 - TcpServer.cc:93
20131108 11:33:23.111680Z 8628 DEBUG TcpConnection TcpConnection::ctor[TestServer:0.0.0.0:8888#2] at 0x8C85128 fd=21 - TcpConnection.cc:62
20131108 11:33:23.111693Z 8628 TRACE newConnection [1] usecount=1 - TcpServer.cc:111
20131108 11:33:23.111722Z 8628 TRACE newConnection [2] usecount=2 - TcpServer.cc:113
20131108 11:33:23.111746Z 8628 TRACE newConnection [5] usecount=3 - TcpServer.cc:122
20131108 11:33:23.111769Z 8630 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 11:33:23.111792Z 8630 TRACE printActiveChannels {13: IN} - EventLoop.cc:257
20131108 11:33:23.111805Z 8630 TRACE connectEstablished [3] usecount=3 - TcpConnection.cc:78
20131108 11:33:23.111813Z 8630 TRACE updateChannel fd = 21 events = 3 - EPollPoller.cc:104
onConnection(): new connection [TestServer:0.0.0.0:8888#2] from 127.0.0.1:56412
20131108 11:33:23.111836Z 8630 TRACE connectEstablished [4] usecount=3 - TcpConnection.cc:83
20131108 11:33:25.219778Z 8631 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:25.219829Z 8632 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:28.969971Z 8629 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:33.119151Z 8630 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:33.119202Z 8628 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:33.754975Z 8629 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 11:33:33.755031Z 8629 TRACE printActiveChannels {20: IN} - EventLoop.cc:257
20131108 11:33:33.755042Z 8629 TRACE handleEvent [6] usecount=2 - Channel.cc:67
onMessage(): received 6 bytes from connection [TestServer:0.0.0.0:8888#1]
20131108 11:33:33.755128Z 8629 TRACE handleEvent [12] usecount=2 - Channel.cc:69
20131108 11:33:35.230224Z 8631 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:35.230274Z 8632 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:36.540663Z 8630 TRACE poll 1 events happended - EPollPoller.cc:65
20131108 11:33:36.540715Z 8630 TRACE printActiveChannels {21: IN} - EventLoop.cc:257
20131108 11:33:36.540727Z 8630 TRACE handleEvent [6] usecount=2 - Channel.cc:67
onMessage(): received 7 bytes from connection [TestServer:0.0.0.0:8888#2]
20131108 11:33:36.540780Z 8630 TRACE handleEvent [12] usecount=2 - Channel.cc:69
20131108 11:33:43.129769Z 8628 TRACE poll nothing happended - EPollPoller.cc:74
20131108 11:33:43.765633Z 8629 TRACE poll nothing happended - EPollPoller.cc:74
一個程式本來被開啟的檔案描述符就有0,1,2;
每個Reactor 的 EventLoop 物件構造時,預設使用的是EPollPoller,即EPollPoller::epollfd_ ;
此外還有兩個channel(EventLoop::timeQueue_ ::timerfd_ 和 EventLoop::wakeupFd_ )
處於被poll()關注可讀事件的狀態,而且是一直關注直到事件迴圈結束。
即每個Reactor 都分別有這3個fd;
對於mainReactor來說,還有Acceptor::acceptSocket_.sockfd_ (listenfd); Acceptor::idleFd_ ; (/dev/null)
按上述程式來說,mainReactor中:epollfd_ = 3; timerfd_ = 4; wakeupFd_ = 5; sockfd_ = 6; idleFd_ = 7;
(8,9,10),(11,12,13),(14,15,16),(17,18,19) 分別歸4個IO執行緒所有
這樣已連線套接字只能從20開始,而且均勻分配到4個subReactor 處理事件(可讀事件(包括接收資料,連線關閉),可寫事件(核心傳送緩衝區不為滿),錯誤事件)
當第一個客戶端連線上來時,sockfd_ 可讀,mainReactor 呼叫 TcpServer::newConnection(), 建立一個TcpConnection物件,繫結到執行緒池中的第一個IO執行緒上,函式內呼叫ioLoop->runInLoop(); 會喚醒第一個IO執行緒,即第一個IO執行緒的wakeupFd_ (10)可讀,handleEvent() 處理後繼續處理doPendingFunctors(),執行TcpConnection::connectEstablished(),接下去的流程包括接收資料(fd
= 20 可讀)可以參考EventLoop(三)的描述。
當然如果我們傳遞的numThreads_ = 0 或者不傳遞; 即只有一個mainReactor, 則監聽套接字和已連線套接字事件都要由這個mainReactor處理。
參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》
相關文章
- muduo網路庫Timestamp類
- muduo網路庫Exception異常類Exception
- muduo網路庫AtomicIntegerT原子整數類
- muduo網路庫編譯安裝編譯
- gRPC學習之四:實戰四類服務方法RPC
- 深度學習(四)之電影評論分類深度學習
- muduo原始碼解析11-logger類原始碼
- 機器學習之多類別神經網路:Softmax機器學習神經網路
- 四類NoSQL資料庫SQL資料庫
- Python 學習筆記之類「物件導向,超類,抽象」Python筆記物件抽象
- Python 客戶端類庫之paho-mqtt學習總結Python客戶端MQQT
- 【拉勾教育】學習筆記之集合類庫(Iterator、foreach、List、Queue)筆記
- Python pyinstaller類庫使用學習總結Python
- Python pycryptodome類庫使用學習總結Python
- Python pymodbus類庫使用學習總結Python
- JAVA類庫之——Character類(持續更新)Java
- 機器學習之多類別神經網路:一對多機器學習神經網路
- 網路安全學習中,原始碼審計有哪些分類?原始碼
- TypeScript學習筆記之五類(Class)TypeScript筆記
- 機器學習-聚類分析之DBSCAN機器學習聚類
- 機器學習 之 層次聚類機器學習聚類
- 深度學習(二)之貓狗分類深度學習
- PHP 手冊 (類與物件) 學習筆記四:類的自動載入PHP物件筆記
- C#學習——基本類——Math類C#
- 2018-05-19學習小結 - 儲存類的倉庫-Java常用類庫9Java
- 四大類NOSQL資料庫SQL資料庫
- Python學習手冊之類和繼承Python繼承
- 學習Source Generators之從swagger中生成類Swagger
- java基礎學習之類集框架(十)Java框架
- 深度學習之新聞多分類問題深度學習
- educoder上的實訓題目(學習-Java包裝類之Byte類)Java
- 深度學習之Transformer網路深度學習ORM
- Python clickhouse-driver 類庫使用學習總結Python
- 實驗四 類的組合、繼承、模板類、標準庫繼承
- 實驗四 類的組合,繼承,模板類,標準庫繼承
- TypeScript學習(三)—— 類TypeScript
- Java 學習路線之四個階段Java
- 網路安全的型別主要有哪些?四大類!型別
- Python 3 學習筆記之類與例項Python筆記