Pull to refresh

Comments 19

UFO just landed and posted this here
Его вообще нет на github'е.

Насколько я знаю — нет, даже, если бы они были, то accept потокобезопасный. Он сработал бы только в 1 потоке, в остальных был бы EAGAIN.
> Я знаю 3 популярных варианта работы с epoll в многопоточном приложении:
Есть еще 4-й способ:
N потоков на одном epoll-е (без отдельного потока, который принимает соединения). То есть, каждый поток будет регистрировать новые сокеты (если эта работа достанется ему). Дополнительно можно провести оптимизацию: попытаться сразу прочитать из зарегестрированного сокета, не добавляя его в epoll, вдруг туда уже что-то записали
UFO just landed and posted this here
Нужно при вызове epoll_ctl указывать флаг EPOLLONESHOT. Тогда пробуждаться будет один поток.
Насчет реальных примеров, сам сталкивался только с примерами n потоков n epoll-ов. Почему делают именно так, сам не знаю, может, так реально лучше. Но мне всегда казалось, что вариант с 1-м epoll-ом правильнее (нагрузка распределена между всеми потоками, значит, не будет проседаний у какого-то потока)
UFO just landed and posted this here
Если я правильно помню, вместо EPOLLONESHOT можно указывать EPOLLET, если читать сокет до конца (по условию задачи, это возможно)
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).
Большое спасибо за статью! Если не сложно, добавьте тег highloadcup, её потом можно будет легко найти, когда все начнут готовиться ко второму чемпионату :)
Пока не знаем, у нас aicups.ru сейчас вовсю. Потом наверное, выдохнем немножко…
TCP_QUICKACK в вашей ситуации скорее вреден, т.к. и так почти сразу полетит ответ, который и сообщит о новом значении SEQ_ACK. Но я не понимаю другое: какое время действует этот флаг? Мне приходилось ставить его после каждого чтения, чтобы быть уверенным, что ACK ушёл.

Как я понял, этот флаг имеет значение, если клиент его также поддерживает. В случае танка, я в этом сомневаюсь.

Хорошая статья!
И замечание по роутингу:


strncmp(path.Data, "/users", 6) == 0

Такая проверка сработает и на /usersevil. Конечно, неизвестно, что там скрыто в handleGetUser(), но лучше проверять длина + 1 и следующий байт.

Здесь важно было только то, что путь начинается с /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();
}

Это я все понимаю :)
Я про то, что также будут "нормально" роутиться пути вида
/usersevil/123/, но внутри хэндлера они все-таки будут обработаны правильно.

Я обычно, при роутинге, вместо множества strncmp подряд поступаю так:

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, эта оптимизация роли не сыграет.
Sign up to leave a comment.

Articles