Recentemente, fiz uma pergunta com o título "Is malloc thread safe?" , e dentro disso perguntei: "Malloc é reentrante?"
Fiquei com a impressão de que todos os reentrantes são seguros para discussão.
Esta suposição está errada?
Recentemente, fiz uma pergunta com o título "Is malloc thread safe?" , e dentro disso perguntei: "Malloc é reentrante?"
Fiquei com a impressão de que todos os reentrantes são seguros para discussão.
Esta suposição está errada?
Respostas:
Funções re-entrantes não dependem de variáveis globais que são expostas nos cabeçalhos da biblioteca C .. pegue strtok () vs strtok_r () por exemplo em C.
Algumas funções precisam de um local para armazenar um 'trabalho em andamento', funções reentrantes permitem que você especifique este ponteiro dentro do armazenamento do próprio thread, não em um global. Uma vez que este armazenamento é exclusivo para a função de chamada, ele pode ser interrompido e reinserido (reentrante) e, uma vez que na maioria dos casos, a exclusão mútua além do que a função implementa não é necessária para que isso funcione, eles são frequentemente considerados discussão segura . Isso não é, entretanto, garantido por definição.
errno, no entanto, é um caso ligeiramente diferente em sistemas POSIX (e tende a ser estranho em qualquer explicação de como tudo isso funciona) :)
Resumindo, reentrant geralmente significa thread safe (como em "use a versão reentrant dessa função se estiver usando threads"), mas thread safe nem sempre significa reentrante (ou o contrário). Quando você está olhando para thread-safety, a simultaneidade é o que você precisa pensar. Se você tiver que fornecer um meio de bloqueio e exclusão mútua para usar uma função, então a função não é inerentemente thread-safe.
Mas, nem todas as funções precisam ser examinadas para qualquer um. malloc()
não precisa ser reentrante, não depende de nada fora do escopo do ponto de entrada para qualquer thread (e é seguro para thread).
Funções que retornam valores alocados estaticamente não são thread-safe sem o uso de um mutex, futex ou outro mecanismo de bloqueio atômico. No entanto, eles não precisam ser reentrantes se não forem interrompidos.
ie:
static char *foo(unsigned int flags)
{
static char ret[2] = { 0 };
if (flags & FOO_BAR)
ret[0] = 'c';
else if (flags & BAR_FOO)
ret[0] = 'd';
else
ret[0] = 'e';
ret[1] = 'A';
return ret;
}
Então, como você pode ver, ter vários threads usando isso sem algum tipo de bloqueio seria um desastre ... mas não tem o propósito de ser reentrado. Você encontrará isso quando a memória alocada dinamicamente for um tabu em alguma plataforma embarcada.
Na programação puramente funcional, reentrant muitas vezes não implica thread-safe, dependeria do comportamento de funções definidas ou anônimas passadas para o ponto de entrada da função, recursão, etc.
A melhor maneira de colocar 'thread safe' é segura para acesso simultâneo , o que ilustra melhor a necessidade.
TL; DR: uma função pode ser reentrante, thread-safe, ambas ou nenhuma.
Os artigos da Wikipedia para thread-safety e reentrância valem a pena ler. Aqui estão algumas citações:
Uma função é thread-safe se:
ele apenas manipula estruturas de dados compartilhadas de uma maneira que garante uma execução segura por vários threads ao mesmo tempo.
Uma função é reentrante se:
ele pode ser interrompido em qualquer ponto durante sua execução e então seguramente chamado novamente ("reinserido") antes que suas invocações anteriores completem a execução.
Como exemplos de possíveis reentradas, a Wikipedia dá o exemplo de uma função projetada para ser chamada por interrupções do sistema: suponha que já esteja em execução quando outra interrupção ocorrer. Mas não pense que você está seguro apenas porque não codifica com interrupções do sistema: você pode ter problemas de reentrada em um programa de thread único se usar callbacks ou funções recursivas.
A chave para evitar confusão é que reentrant se refere a apenas um thread em execução. É um conceito da época em que não existiam sistemas operacionais multitarefa.
Exemplos
(Ligeiramente modificado dos artigos da Wikipedia)
Exemplo 1: não thread-safe, não reentrante
/* As this function uses a non-const global variable without
any precaution, it is neither reentrant nor thread-safe. */
int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Exemplo 2: thread-safe, não reentrante
/* We use a thread local variable: the function is now
thread-safe but still not reentrant (within the
same thread). */
__thread int t;
void swap(int *x, int *y)
{
t = *x;
*x = *y;
*y = t;
}
Exemplo 3: não thread-safe, reentrante
/* We save the global state in a local variable and we restore
it at the end of the function. The function is now reentrant
but it is not thread safe. */
int t;
void swap(int *x, int *y)
{
int s;
s = t;
t = *x;
*x = *y;
*y = t;
t = s;
}
Exemplo 4: thread-safe, reentrant
/* We use a local variable: the function is now
thread-safe and reentrant, we have ascended to
higher plane of existence. */
void swap(int *x, int *y)
{
int t;
t = *x;
*x = *y;
*y = t;
}
t = *x
, chama swap()
, então t
será substituído, levando a resultados inesperados.
swap(5, 6)
ser interrompido por a swap(1, 2)
. Depois t=*x
, s=t_original
e t=5
. Agora, após a interrupção, s=5
e t=1
. No entanto, antes que o segundo swap
retorne, ele irá restaurar o contexto, fazendo t=s=5
. Agora, voltamos ao primeiro swap
com t=5 and s=t_original
e continuamos depois t=*x
. Portanto, a função parece ser reentrante. Lembre-se de que cada chamada obtém sua própria cópia de s
alocada na pilha.
Depende da definição. Por exemplo Qt usa o seguinte:
Uma função thread-safe * pode ser chamada simultaneamente a partir de vários threads, mesmo quando as invocações usam dados compartilhados, porque todas as referências aos dados compartilhados são serializadas.
UMA função reentrante também pode ser chamada simultaneamente a partir de vários encadeamentos, mas apenas se cada chamada usar seus próprios dados.
Portanto, um função thread-safe é sempre reentrante, mas uma função reentrante nem sempre é thread-safe.
Por extensão, uma classe é considerada reentrante se suas funções-membro puderem ser chamadas com segurança a partir de vários threads, desde que cada thread use uma instância diferente da classe. A classe é segura para thread se suas funções de membro puderem ser chamadas com segurança de vários threads, mesmo se todos os threads usarem a mesma instância da classe.
mas também alertam:
Observação: a terminologia no domínio multithreading não é totalmente padronizada. POSIX usa definições de reentrant e thread-safe que são um pouco diferentes para suas APIs C. Ao usar outras bibliotecas de classes C ++ orientadas a objetos com Qt, certifique-se de que as definições sejam compreendidas.