Thread safety na libc

Estou reescrevendo um programa que usa fork() para criar sub-processos, que agem como se fossem múltiplas threads, para o uso explícito de phreads. Eis alguns problemas sérios: Como saber se uma função da biblioteca padrão é thread safe? Quais funções thread safe são também atômicas?

Se uma função é dita thread safe, isso indica somente que ela é internamente segura para ser usada num ambiente multithreaded. Ou seja, a função mantém seu estado interno próprio, não compartilhado. Evitar o uso de variáveis globais e do heap faz a mágica… Isso não significa que não precisaremos usar algum recurso de sincronização no uso dessa função. Tomemos memcpy() como exemplo. Ela é thread safe, segundo a documentação de libc, mas se a cópia for feita no mesmo buffer em mais de uma thread poderemos ter problemas. Se uma função é thread safe internamente, não quer dizer que seja externamente.

E outro cuidado: Só porque uma função é internamente thread safe, não significa que seja atômica. A atomicidade, aqui, significa que a função será excutada integralmente antes que possa haver um task switch. De fato, libc e POSIX não dizem nada sobre a atomicidade de funções. As únicas maneiras de garantir atomicidade é usando mecanismos de sincronização (como mutexes da pthread ou GCC Atomic Built-ins [veja aqui] – embora seja preferível, para efeitos de compatibilidade, usar a pthread!). Outra maneira de garantir atomicidade no acesso a uma variável, embora não me sinta confortável com esse método em particular, é usar o modificador _Atomic ou __attribute__((atomic)) [no caso do GCC]. O desconforto vem do fato de esse ser um recurso da especificação C++11 e não de C99 (ou GNU99) — e, pelo que li, isso ainda é meio experimental…

As manpages costumam dar importantes dicas sobre as funções (especialmente as sessões 2 e 3), mas costumam não dizer quase nada sobre thread safety. É interessante consultar a documentação oficial da libc (baixe aqui). Todas as funções listadas no manual são comentadas de acordo com o tópico 1.2.2.1 (POSIX Safety Concepts). Eis um exemplo de documentação da função sendto():

ssize_t sendto (int socket, const void *buffer, size t size, int      [Function]
        flags, struct sockaddr *addr, socklen t length)
    Preliminary: | MT-Safe | AS-Safe | AC-Safe | See Section 1.2.2.1 [POSIX Safety
    Concepts], page 2.

    The sendto function transmits the data in the buffer through the socket socket to the
    destination address specified by the addr and length arguments. The size argument
    specifies the number of bytes to be transmitted.
    ...

No comentário “Preliminary” temos “MT-Safe” (MultiThread Safe), portanto sendto() é internamente thread safe.

Eis um exemplo de função que não é thread safe:

char * strtok (char *restrict newstring, const char *restrict        [Function]
        delimiters)
    Preliminary: | MT-Unsafe race:strtok | AS-Unsafe | AC-Safe | See Section 1.2.2.1
[POSIX Safety Concepts], page 2.
...

A manpage do strtok nos mostra a função alternativa, thread safe: strtok_r(). O macete é que strtok_r() precisa de algum lugar externo para manter seu próprio estado. Você pode alocar um buffer para cada thread e usá-lo para manter esse estado. Isso vale para outras funções, como random(), por exemplo, que tem seu alter-ego random_r().

Resumindo, criar programas multitheraded dá trabalho! Especialmente porque você terá que ficar atento para tentar usar funções internamente thread safe, evitando as condições de corrida (race conditions) — ou seja, garantindo que as chamadas às funções sejam externamente thread safe — além de levar em conta a não atomicidade das funções.

Anúncios

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s