Comments 19
UFO just landed and posted this here
> Я знаю 3 популярных варианта работы с epoll в многопоточном приложении:
Есть еще 4-й способ:
N потоков на одном epoll-е (без отдельного потока, который принимает соединения). То есть, каждый поток будет регистрировать новые сокеты (если эта работа достанется ему). Дополнительно можно провести оптимизацию: попытаться сразу прочитать из зарегестрированного сокета, не добавляя его в epoll, вдруг туда уже что-то записали
Есть еще 4-й способ:
N потоков на одном epoll-е (без отдельного потока, который принимает соединения). То есть, каждый поток будет регистрировать новые сокеты (если эта работа достанется ему). Дополнительно можно провести оптимизацию: попытаться сразу прочитать из зарегестрированного сокета, не добавляя его в epoll, вдруг туда уже что-то записали
0
UFO just landed and posted this here
Нужно при вызове epoll_ctl указывать флаг EPOLLONESHOT. Тогда пробуждаться будет один поток.
Насчет реальных примеров, сам сталкивался только с примерами n потоков n epoll-ов. Почему делают именно так, сам не знаю, может, так реально лучше. Но мне всегда казалось, что вариант с 1-м epoll-ом правильнее (нагрузка распределена между всеми потоками, значит, не будет проседаний у какого-то потока)
Насчет реальных примеров, сам сталкивался только с примерами n потоков n epoll-ов. Почему делают именно так, сам не знаю, может, так реально лучше. Но мне всегда казалось, что вариант с 1-м epoll-ом правильнее (нагрузка распределена между всеми потоками, значит, не будет проседаний у какого-то потока)
0
UFO just landed and posted this here
Если я правильно помню, вместо EPOLLONESHOT можно указывать EPOLLET, если читать сокет до конца (по условию задачи, это возможно)
0
UFO just landed and posted this here
Это разные вещи: дескриптор с EPOLLONESHOT после первого срабатывания не будет больше срабатывать до тех пор, пока его снова не добавить с помощью EPOLL_CTL_MOD (и только с этим флагом пробуждаются все потоки), а EPOLLET, в свою очередь, срабатывает только на каком-то одном потоке, и не будет вновь срабатывать, пока все данные из сокета не прочитаны. И в теории дескриптор не нужно переустанавливать с помощью EPOLL_CTL_MOD.
Хотя ман говорит, что
Since even with edge-triggered epoll, multiple events can be generated upon receipt of multiple chunks of data, the caller has the option to specify the EPOLLONESHOT flag, to tell epoll to disable the associated file descriptor after the receipt of an event with epoll_wait(2).
0
Большое спасибо за статью! Если не сложно, добавьте тег highloadcup, её потом можно будет легко найти, когда все начнут готовиться ко второму чемпионату :)
0
TCP_QUICKACK в вашей ситуации скорее вреден, т.к. и так почти сразу полетит ответ, который и сообщит о новом значении SEQ_ACK. Но я не понимаю другое: какое время действует этот флаг? Мне приходилось ставить его после каждого чтения, чтобы быть уверенным, что ACK ушёл.
0
Хорошая статья!
И замечание по роутингу:
strncmp(path.Data, "/users", 6) == 0
Такая проверка сработает и на /usersevil
. Конечно, неизвестно, что там скрыто в handleGetUser()
, но лучше проверять длина + 1
и следующий байт.
0
Здесь важно было только то, что путь начинается с
Это всё уже внутри
/users
, т.к. дальше возможны варианты:/users/1
/users/1/visits
/users/zfsdfasdadsdf
Это всё уже внутри
handleGetUser()
handleGetUser
void Connection::handlerGetUser() {
char *endptr;
auto id = strtol(path.Data + 7, &endptr, 10);
if (endptr[0] != 0 && endptr[0] != '/') {
WriteNotFound();
return;
}
if (id >= USERS_CNT || db.users[id].Fields == 0) {
WriteNotFound();
return;
}
if (endptr[0] == 0) {
db.users[id].MarshalJSON(outBuf);
WriteResponse();
return;
}
if (strncmp(endptr, "/visits", 7) != 0) {
WriteNotFound();
return;
}
uint64_t fromDate = 0;
uint64_t toDate = 0;
char country[101];
country[0] = 0;
uint64_t toDistance = 0;
endptr += 7;
while (true) {
switch (endptr[0]) {
case '?':
case '&':
++endptr;
break;
}
if (endptr[0] == 0) {
break;
} else if (strncmp(endptr, "fromDate=", 9) == 0) {
fromDate = strtol(endptr + 9, &endptr, 10);
} else if (strncmp(endptr, "toDate=", 7) == 0) {
toDate = strtol(endptr + 7, &endptr, 10);
} else if (strncmp(endptr, "country=", 8) == 0) {
endptr += 8;
auto pos = strchr(endptr, '&');
if (pos != NULL) {
pos[0] = 0;
percent_decode(country, endptr);
pos[0] = '&';
endptr = pos;
} else {
percent_decode(country, endptr);
break;
}
} else if (strncmp(endptr, "toDistance=", 11) == 0) {
toDistance = strtol(endptr + 11, &endptr, 10);
} else {
WriteBadRequest();
return;
}
}
outBuf.Append("{\"visits\":[");
auto first = true;
for (auto it = db.users[id].visits->cbegin();
it != db.users[id].visits->cend(); ++it) {
if ((fromDate == 0 || db.visits[*it].VisitedAt > fromDate)
&& (country[0] == 0
|| strcmp(db.locations[db.visits[*it].Location].Country,
country) == 0)
&& (toDistance == 0
|| toDistance
> db.locations[db.visits[*it].Location].Distance)) {
if (toDate != 0 && db.visits[*it].VisitedAt > toDate) {
break;
}
if (!first) {
outBuf.Append(",{\"mark\":");
} else {
first = false;
outBuf.Append("{\"mark\":");
}
outBuf.AddLen(
hl_write_string(uint64_t(db.visits[*it].Mark), outBuf.End));
outBuf.Append(",\"visited_at\":");
outBuf.AddLen(
hl_write_string(db.visits[*it].VisitedAt, outBuf.End));
outBuf.Append(",\"place\":\"");
outBuf.Append(db.locations[db.visits[*it].Location].Place);
outBuf.Append("\"}");
}
}
outBuf.Append("]}");
WriteResponse();
}
0
Я обычно, при роутинге, вместо множества strncmp подряд поступаю так:
Это уменьшит количество ветвлений и количество вызовов strncmp. С другой стороны, если вы упёрлись во write, эта оптимизация роли не сыграет.
switch (path.size) {
case 5:
if (strncmp(path.data, "/path", 5) == 0) {
...
}
break;
case 6:
if (strncmp(path.data, "/path2", 6) == 0) {
...
}
break;
...
}
Это уменьшит количество ветвлений и количество вызовов strncmp. С другой стороны, если вы упёрлись во write, эта оптимизация роли не сыграет.
0
Sign up to leave a comment.
Мои 5 копеек про Highload Cup 2017 или история 9го места