Código reentrante e código de rossão
Em processos de encadeados únicos, apenas um fluxo de controle existe. O código executado por esses processos, portanto, não precisa ser reentrante ou threadsafe. Em programas multithreaded, as mesmas funções e os mesmos recursos podem ser acessados simultaneamente por vários fluxos de controle.
Para proteger a integridade do recurso, o código escrito para programas multiencadeados deve ser reentrante e rossseguro.
A reentrada e a segurança do fio estão ambas relacionadas com a forma que as funções manejam recursos. Reentrada e segurança de encadeamento são conceitos separados: uma função pode ser reentrante, threadsafe, ambas, ou nem tampouco.
Esta seção fornece informações sobre a escrita de programas reentrantes e rosseguros. Ele não cobre o tópico de escrever programas eficientes de encadeamento. Programas eficientes de encadeamento são programas paralelizados eficientemente. Você deve considerar a eficiência de encadeamento durante o design do programa. Os programas de threaded existentes podem ser feitos de forma eficiente, mas isso requer que eles sejam completamente redesenhados e reescritos.
Reentrada
Uma função reentrante não mantém dados estáticos sobre chamadas sucessivas, nem devolvem um ponteiro aos dados estáticos. Todos os dados são fornecidos pelo ouvinte da função. Uma função reentrante não deve chamar funções não reentrantes.
Uma função não reentrante pode muitas vezes, mas nem sempre, ser identificada por sua interface externa e seu uso. Por exemplo, o subroutine strtok não é reentrante, pois ele mantém a string a ser quebrada em tokens. O subroutine ctime também não é reentrante; ele retorna um ponteiro para dados estáticos que são sobrescritos por cada chamada.
Segurança do fio
Uma função threadsafe protege recursos compartilhados de acessos simultâneos por bloqueios. A segurança do fio diz respeito apenas à implementação de uma função e não afeta a sua interface externa.
/* threadsafe function */
int diff(int x, int y)
{
int delta;
delta = y - x;
if (delta < 0)
delta = -delta;
return delta;
}O uso de dados globais é thread-insafe. Os dados globais devem ser mantidos por thread ou encapsulados, de modo que seu acesso possa ser serializado. Uma thread pode ler um código de erro correspondente a um erro causado por outro encadeamento. No AIX®, cada thread tem seu próprio valor errno.
Fazendo uma função reentrante
Na maioria dos casos, as funções não reentrantes devem ser substituídas por funções com uma interface modificada para ser reentrante. Funções não reentrantes não podem ser usadas por várias threads. Além disso, pode ser impossível fazer uma função não reentrante threadsafe.
Retornando dados
- Retornando dados alocados dinamicamente. Neste caso, será a responsabilidade do ouvinte liberar o armazenamento. O benefício é que a interface não precisa ser modificada. No entanto, a compatibilidade com backward não é assegurada; os programas de threaded existentes usando as funções modificadas sem alterações não liberariam o armazenamento, levando a vazamentos de memória.
- Usando armazenamento fornecido pelo caller. Este método é recomendado, embora a interface deve ser modificada.
/* non-reentrant function */
char *strtoupper(char *string)
{
static char buffer[MAX_STRING_SIZE];
int index;
for (index = 0; string[index]; index++)
buffer[index] = toupper(string[index]);
buffer[index] = 0
return buffer;
}/* reentrant function (a poor solution) */
char *strtoupper(char *string)
{
char *buffer;
int index;
/* error-checking should be performed! */
buffer = malloc(MAX_STRING_SIZE);
for (index = 0; string[index]; index++)
buffer[index] = toupper(string[index]);
buffer[index] = 0
return buffer;
}/* reentrant function (a better solution) */
char *strtoupper_r(char *in_str, char *out_str)
{
int index;
for (index = 0; in_str[index]; index++)
out_str[index] = toupper(in_str[index]);
out_str[index] = 0
return out_str;
}As subroutines de biblioteca padrão C não reentrantes foram tornadas reentrantes usando armazenamento fornecido pelo caller.
Manter os dados sobre as chamadas sucessivas
Nenhum dado deve ser mantido sobre as chamadas sucessivas, pois diferentes threads podem sucessivamente chamar a função. Se uma função deve manter alguns dados sobre chamadas sucessivas, como um buffer de trabalho ou um ponteiro, o ouvinte deverá fornecer esses dados.
/* non-reentrant function */
char lowercase_c(char *string)
{
static char *buffer;
static int index;
char c = 0;
/* stores the string on first call */
if (string != NULL) {
buffer = string;
index = 0;
}
/* searches a lowercase character */
for (; c = buffer[index]; index++) {
if (islower(c)) {
index++;
break;
}
}
return c;
}h
/* reentrant function */
char reentrant_lowercase_c(char *string, int *p_index)
{
char c = 0;
/* no initialization - the caller should have done it */
/* searches a lowercase character */
for (; c = string[*p_index]; (*p_index)++) {
if (islower(c)) {
(*p_index)++;
break;
}
}
return c;
}char *my_string;
char my_char;
int my_index;
...
my_index = 0;
while (my_char = reentrant_lowercase_c(my_string, &my_index)) {
...
}Fazendo uma função threadsafe
Em programas multithreaded, todas as funções chamadas por múltiplas threads devem ser threadsafe. No entanto, uma solução alternativa existe para usar sub-rotinas sem segurança em programas multithreaded. Funções não reentrantes geralmente são thread-inseguras, mas fazê-las reentrantes muitas vezes torna-as a treadsafe, também.
Bloqueio de recursos compartilhados
/* thread-unsafe function */
int increment_counter()
{
static int counter = 0;
counter++;
return counter;
}/* pseudo-code threadsafe function */
int increment_counter();
{
static int counter = 0;
static lock_type counter_lock = LOCK_INITIALIZER;
pthread_mutex_lock(counter_lock);
counter++;
pthread_mutex_unlock(counter_lock);
return counter;
}Em um programa de aplicativos multiencadeados usando a biblioteca threads, mutexes devem ser usados para serialização de recursos compartilhados. As bibliotecas independentes podem precisar trabalhar fora do contexto de threads e, assim, utilizar outros tipos de bloqueios.
Workarounds para funções inseguras de thread
- Use um bloqueio global para a biblioteca, e bloqueie-a cada vez que você usar a biblioteca (chamando uma rotina de biblioteca ou usando uma variável global de biblioteca). Esta solução pode criar gargalos de desempenho porque apenas uma thread pode acessar qualquer parte da biblioteca em qualquer momento. A solução no seguinte pseudocódigo é aceitável apenas se a biblioteca raramente for acessada, ou como inicial, rapidamente implementada a solução alternativa.
/* this is pseudo code! */ lock(library_lock); library_call(); unlock(library_lock); lock(library_lock); x = library_var; unlock(library_lock); - Use um bloqueio para cada componente da biblioteca (rotina ou variável global) ou grupo de componentes. Essa solução é um pouco mais complicada de implementar do que o exemplo anterior, mas pode melhorar o desempenho. Como esse workaround só deve ser usado em programas de aplicativos e não em bibliotecas, mutexes podem ser usados para bloqueio da biblioteca.
/* this is pseudo-code! */ lock(library_moduleA_lock); library_moduleA_call(); unlock(library_moduleA_lock); lock(library_moduleB_lock); x = library_moduleB_var; unlock(library_moduleB_lock);
Bibliotecas reentrantes e reajustes
As bibliotecas reentrantes e reajustes são úteis em uma ampla gama de ambientes de programação paralelos (e assíncronos), não apenas dentro de threads. É uma boa prática de programação para sempre utilizar e escrever funções reentrantes e de rossão.
Usando bibliotecas
- Biblioteca C padrão (libc.a)
- Biblioteca de compatibilidade Berkeley (libbsd.a)
Algumas das subroutines C padrão são não reentrantes, como as subroutines ctime e strtok . A versão reentrante das subroutines tem o nome da subroutina original com um sufixo _r (sublinhado seguido da letra r).
token[0] = strtok(string, separators);
i = 0;
do {
i++;
token[i] = strtok(NULL, separators);
} while (token[i] != NULL);char *pointer;
...
token[0] = strtok_r(string, separators, &pointer);
i = 0;
do {
i++;
token[i] = strtok_r(NULL, separators, &pointer);
} while (token[i] != NULL);Bibliotecas inseguras de encadeamento podem ser usadas por apenas uma thread em um programa. Garantir a exclusividade do encadeamento usando a biblioteca; caso contrário, o programa terá um comportamento inesperado, ou pode até parar.
Convertendo bibliotecas
- Identificar variáveis globais exportadas. Essas variáveis geralmente são definidas em um arquivo de cabeçalho com a palavra-chave export . As variáveis globais exportadas devem ser encapsuladas. A variável deve ser feita privada (definida com a palavra-chave static no código-fonte da biblioteca) e as subroutines de acesso (leitura e escrita) devem ser criadas.
- Identificar variáveis estáticas e outros recursos compartilhados. As variáveis estáticas são geralmente definidas com a palavra-chave static . Os bloqueios devem ser associados a qualquer recurso compartilhado. A granularidade do travamento, escolhendo assim o número de bloqueios, impacta o desempenho da biblioteca. Para inicializar os bloqueios, pode ser utilizada a instalação de inicialização única.
- Identificar funções não reentrantes e torná-las reentrantes. Para obter mais informações, consulte Making a Function Reentrant.
- Identificar funções inseguras de thread e torná-las threadsafe. Para obter mais informações, consulte Making a Function threadsafe.