Python C Расширение Нужно ли Py_INCREF заимствованная ссылка, если она не возвращена в Python Land? - PullRequest
3 голосов
/ 23 января 2020

Если вы получаете заемную ссылку, но НЕ возвращаете ее на Python землю, следует ли вам делать Py_INCREF, когда вы ее получите, и затем Py_DECREF, когда вы закончите с ней, или это излишне?

Например, этот тривиальный пример получает заимствованную ссылку из PyDict_GetItem, делает некоторые вещи с заимствованной ссылкой, а затем возвращает None в Python землю. Есть ли необходимость в Py_INCREF / Py_DECREF заимствованной ссылке при работе с ней здесь?

static PyObject PyFoo_bar(PyFoo *self, int field)
{
    int field = 0;

    PyFoo *child = NULL;

    if (!PyArg_ParseTuple(args, "i", &field) {
         return NULL;
    }

    child = PyDict_GetItem(self->children, field);

    // Long code omitted that works on child
    // Is it safe to do so without a Py_INCREF on child now followed by PY_DECREF later?

    // NOT returning child here - just return None
    Py_RETURN_NONE;
}

Меня беспокоит то, что после вызова PyDict_GetItem счетчик ссылок child может каким-то образом упасть до нуля, а затем освободить, пока я работаю с ним. Это вообще возможно? Я так не думаю, так как GIL здесь не опущен, но я не уверен.

С другой стороны, мне интересно, будет ли это просто наилучшей практикой делать Py_INCREF / Py_DECREF Вот. Возможно, позже я оставлю GIL, или позже решу вернуть ребенка. Py_INCREF дешево. Например, было бы лучше сделать это так:

child = PyDict_GetItem(self->children, field);
Py_INCREF(child);
// Long code omitted
Py_DECREF(child);
Py_RETURN_NONE;

1 Ответ

1 голос
/ 23 января 2020

Это зависит от того, что делает промежуточный «длинный код». Если он выполняет код Python, который вы не можете контролировать, то вполне возможно, что этот код получает доступ к диктовке self->children и удаляет field, и в этот момент да, его отсчет может упасть до нуля. Таким образом, вы хотите защитить от этого случая и добавить INCREF / DECREF.

Обратите внимание, что любое использование child потребует чтения указателя объекта типа, который находится рядом с счетчиком ссылок, и последний, таким образом, будет были загружены в той же строке кэша. С выполнением не по порядку INCREF / DECREF являются в основном бесплатными операциями, поэтому производительность не является причиной, чтобы их исключать.

Лучшая причина, которую я могу придумать, чтобы не выполняла INCREF , - когда «длинный код» имеет несколько точек выхода (но не выполняет произвольный код python или не касается self->children, как описано выше). Вы должны будете добавить DECREF для каждого выхода, с высоким риском пропустить его и получить утечку памяти, которую трудно отладить.

...