幾乎每一種數據庫都會有連接池, 來減少頻繁的創建刪除連接的開銷, 在MongoDB裏面是通過信號量線程同步方式來對創建、銷燬進行管理。
信號量基礎
int sem_init(sem_t *sem, int pshared, unsigned int value)
sem是要初始化的信號量,pshared表示此信號量是在進程間共享(=1)還是線程間共享(=0),value是信號量的初始值。
int sem_destroy(sem_t *sem);
其中sem是要銷燬的信號量。只有用sem_init初始化的信號量才能用sem_destroy銷燬。
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
sem_wait:等待信號量,如果信號量的值大於0, 將信號量的值減1,立即返回。如果信號量的值爲0,則線程阻塞。相當於P操作。成功返回0,失敗返回-1。
sem_trywait:與sem_wait類似, 只是如果遞減不能立即執行,調用將返回錯誤(errno 設置爲EAGAIN)而不是阻塞。
sem_timewait:sem_wait() 類似,只不過 abs_timeout 指定一個阻塞的時間上限,如果調用因不能立即執行遞減而要阻塞。
int sem_post(sem_t *sem);
釋放信號量,讓信號量的值加1。相當於V操作。
連接池的實現
使用TicketHoder來封裝類線程控制信號量函數:
TicketHolder::TicketHolder(int num) : _outof(num) {
_check(sem_init(&_sem, 0, num));
}
TicketHolder::~TicketHolder() {
_check(sem_destroy(&_sem));
}
bool TicketHolder::tryAcquire() {
while (0 != sem_trywait(&_sem)) {
switch (errno) {
case EAGAIN:
return false;
case EINTR:
break;
default:
_check(-1);
}
}
return true;
}
void TicketHolder::waitForTicket() {
while (0 != sem_wait(&_sem)) {
switch (errno) {
case EINTR:
break;
default:
_check(-1);
}
}
}
void TicketHolder::release() {
_check(sem_post(&_sem));
}
在MongoDB啓動的時候, 會先創建一個PortMessageServer,並在裏面指定一個有配置參數或者命令行參數指定的最大的連接數, 然後通過setupSockets創建一個socket並綁定, 並將其加入到Listener裏面的std::vector _socks;
static void _initAndListen(int listenPort) {
...
auto handler = std::make_shared<MyMessageHandler>();
MessageServer* server = createServer(options, std::move(handler));
server->setAsTimeTracker();
if (!server->setupSockets()) {
error() << "Failed to set up sockets during startup.";
return;
}
...
server->run();
}
MessagePortServer裏面, 通過Listener::initAndListen函數,最終在 PortMessageServer::accepted裏面來創建新的線程處理本次的操作。
這裏可以看到, 每一次新的操作, TicketHoder::tryAcquire會試圖進入信號量的代碼區, 如果信號量的連接數大於1, 就會獲得信號量鎖, 並且將連接數減少1; 如果此時的連接數爲0 , tryAcquire會返回失敗, 表明連接數已滿, 無法連接。
獲得信號量鎖之後, 會建立一下新的處理線程, 指定其處理函數爲:
PortMessageServer::handleIncomingMsg.
class PortMessageServer : public MessageServer, public Listener {
virtual void accepted(std::shared_ptr<Socket> psocket, long long connectionId) {
ScopeGuard sleepAfterClosingPort = MakeGuard(sleepmillis, 2);
std::unique_ptr<MessagingPortWithHandler> portWithHandler(
new MessagingPortWithHandler(psocket, _handler, connectionId));
if (!Listener::globalTicketHolder.tryAcquire()) {
log() << "connection refused because too many open connections: "
<< Listener::globalTicketHolder.used() << endl;
return;
}
{
stdx::thread thr(stdx::bind(&handleIncomingMsg, portWithHandler.get()));
thr.detach();
}
}