[Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

1. [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

M.
XProtoman

(usa Fedora)

Enviado em 03/12/2015 - 15:02h

Boa tarde a todos,

A um tempo tento entender com resolver problemas de threads concorrentes(threads não concorrentes é tranquilo) e estou me deparando com a primeira solução que é o uso de mutex. Fico com um pé atrás e na dúvida: em uma situação de uso simples de MUTEX em threads concorrentes existe o risco de ocorrência de DEADLOCKs?

O uso simples que eu digo é por exemplo: você tem um método GET e um SET, e um mutex para gerencia-los e fazem LOCK logo no inicio e um UNLOCK no final de cada um deles, existe risco de DEADLOCK?

Obrigado.


  


2. MELHOR RESPOSTA

Paulo
paulo1205

(usa Ubuntu)

Enviado em 03/12/2015 - 22:09h

Se for uma só mutex, é totalmente seguro. O risco de deadlock existe se você tiver duas mutexes ou mais, outra thread depender das mesmas mutexes, e uma delas retiver uma delas ao mesmo tempo em que outra detiver aluma das demais.

3. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

M.
XProtoman

(usa Fedora)

Enviado em 03/12/2015 - 22:44h

paulo1205 escreveu:

Se for uma só mutex, é totalmente seguro. O risco de deadlock existe se você tiver duas mutexes ou mais, outra thread depender das mesmas mutexes, e uma delas retiver uma delas ao mesmo tempo em que outra detiver aluma das demais.

Muito obrigado paulo1205 pela resposta, não tenho experiência e foi de grande ajuda! :D





4. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

Uilian Ries
uilianries

(usa Linux Mint)

Enviado em 04/12/2015 - 07:10h

Em C++, existe o caso de poder ocorrer uma exceção antes de ser chamado o unlock. Nesse eh possível ocorrer deadlock.
Nesse caso, std::scoped_lock, que garante o unlock na destruição do objeto (RAII).


std::mutex g_mutex;

void foo() {
std::scoped_lock<std::mutex> lock(g_mutex);
show_data();
}

void bar() {
g_mutex.lock()
show_data();
g_mutex.unlock();
}


O problema aqui está na função bar(), se show_data() lançar uma exceção, unlock() não será chamado. A outra thread que estiver aguardar para se apropriar do mutex, ficará presa.

A primeira função, foo(), utiliza o artifício de scoped lock, uma vez que seja passado o mutex, o construtor chama o lock do mutex, e ao chamar o destrutor de scoped_lock, o unlock do mutex será chamado. Mesmo que show_data() lance uma excessão, o unlock será garantido.


5. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

M.
XProtoman

(usa Fedora)

Enviado em 04/12/2015 - 08:15h

Bom dia uilianries,

Depois da resposta do paulo corri para usar mutex e fazer alguns testes. Quando estava codificando notei que algumas classes realmente poderiam disparar exceções que deixariam o mutex bloqueado, resolvi tratando a exceção e dentro dela realizando também um outro unlock e para não ter riscos estou realizando o lock sempre no início do método, isso também resolve?

Obrigado.


6. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

Uilian Ries
uilianries

(usa Linux Mint)

Enviado em 04/12/2015 - 14:19h


Sim, funciona.


7. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 04/12/2015 - 17:02h

Bom, na minha resposta eu assumi que alguém que consiga o lock vai sempre liberá-lo. Se uma função pede o lock e, por erro de projeto ou implementação, nunca o libera, não é culpa da biblioteca. Esta é segura, mas nem todo programa escrito com ela o será.

Obviamente que existindo um envelopamento com RAII as coisas ficam sempre mais seguras. Isso não vale só para mutexes, mas para todo recurso que possa ter leaks (memória, descritores de arquivos, locks de arquivos etc.).

Contudo, esse std:;scoped_lock, eis algo que não consegui achar no padrão. É coisa da Boost, ou alguma versão em elaboração? O padrão do C++ de 2011 traz std::lock_guard, que acho que faz coisa parecida.


8. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

M.
XProtoman

(usa Fedora)

Enviado em 04/12/2015 - 19:27h

Boa noite, queria mostrar para vocês um erro interessante, segue o código(por enquanto não descomentem aquela linha):


#include <iostream>
#include <mutex>

//#define RESOLVER_PROBLEMA 1

class Teste
{
private:
std::mutex *mutex;
public:
Teste();
Teste(const Teste &teste);
~Teste();
#ifdef RESOLVER_PROBLEMA
void operator = (const Teste &teste);
#endif
};

Teste::Teste()
{
this->mutex = new std::mutex();
}

Teste::Teste(const Teste &teste): Teste()
{
teste.mutex->lock();
teste.mutex->unlock();
}

Teste::~Teste()
{
delete this->mutex;
this->mutex = NULL;
}

#ifdef RESOLVER_PROBLEMA
void Teste::operator = (const Teste &teste)
{
teste.mutex->lock();
teste.mutex->unlock();
}
#endif

int main()
{
Teste teste;
for ( size_t posicao = 0, posicao_maximo = 1000000;
posicao_maximo > posicao;
posicao++ )
{
teste = Teste();
}
return 0;
}



Note que aparentemente não existe nada de errado com o código(alocações e desalocações sendo realizadas ondem devem), porém produz o erro:


$ ./teste_generico.run
*** Error in `./teste_generico.run': double free or corruption (fasttop): 0x0000000001ee1c50 ***
======= Backtrace: =========
/lib64/libc.so.6(+0x77e15)[0x7f3b4ba5fe15]
/lib64/libc.so.6(+0x804fa)[0x7f3b4ba684fa]
/lib64/libc.so.6(cfree+0x4c)[0x7f3b4ba6bcac]
./teste_generico.run[0x400b82]
./teste_generico.run[0x400bf7]
/lib64/libc.so.6(__libc_start_main+0xf0)[0x7f3b4ba08580]
./teste_generico.run[0x400959]
======= Memory map: ========
00400000-00402000 r-xp 00000000 09:7b 39 /tmp/teste_generico.run
00601000-00602000 r--p 00001000 09:7b 39 /tmp/teste_generico.run
00602000-00603000 rw-p 00002000 09:7b 39 /tmp/teste_generico.run
01ed0000-01f02000 rw-p 00000000 00:00 0 [heap]
7f3b44000000-7f3b44021000 rw-p 00000000 00:00 0
7f3b44021000-7f3b48000000 ---p 00000000 00:00 0
7f3b4b9e8000-7f3b4bb9f000 r-xp 00000000 09:7f 1050566 /usr/lib64/libc-2.22.so
7f3b4bb9f000-7f3b4bd9f000 ---p 001b7000 09:7f 1050566 /usr/lib64/libc-2.22.so
7f3b4bd9f000-7f3b4bda3000 r--p 001b7000 09:7f 1050566 /usr/lib64/libc-2.22.so
7f3b4bda3000-7f3b4bda5000 rw-p 001bb000 09:7f 1050566 /usr/lib64/libc-2.22.so
7f3b4bda5000-7f3b4bda9000 rw-p 00000000 00:00 0
7f3b4bdb0000-7f3b4bdc6000 r-xp 00000000 09:7f 1048596 /usr/lib64/libgcc_s-5.1.1-20150618.so.1
7f3b4bdc6000-7f3b4bfc5000 ---p 00016000 09:7f 1048596 /usr/lib64/libgcc_s-5.1.1-20150618.so.1
7f3b4bfc5000-7f3b4bfc6000 r--p 00015000 09:7f 1048596 /usr/lib64/libgcc_s-5.1.1-20150618.so.1
7f3b4bfc6000-7f3b4bfc7000 rw-p 00016000 09:7f 1048596 /usr/lib64/libgcc_s-5.1.1-20150618.so.1
7f3b4bfc8000-7f3b4c0c9000 r-xp 00000000 09:7f 1050574 /usr/lib64/libm-2.22.so
7f3b4c0c9000-7f3b4c2c8000 ---p 00101000 09:7f 1050574 /usr/lib64/libm-2.22.so
7f3b4c2c8000-7f3b4c2c9000 r--p 00100000 09:7f 1050574 /usr/lib64/libm-2.22.so
7f3b4c2c9000-7f3b4c2ca000 rw-p 00101000 09:7f 1050574 /usr/lib64/libm-2.22.so
7f3b4c2d0000-7f3b4c442000 r-xp 00000000 09:7f 1050874 /usr/lib64/libstdc++.so.6.0.21
7f3b4c442000-7f3b4c642000 ---p 00172000 09:7f 1050874 /usr/lib64/libstdc++.so.6.0.21
7f3b4c642000-7f3b4c64c000 r--p 00172000 09:7f 1050874 /usr/lib64/libstdc++.so.6.0.21
7f3b4c64c000-7f3b4c64e000 rw-p 0017c000 09:7f 1050874 /usr/lib64/libstdc++.so.6.0.21
7f3b4c64e000-7f3b4c652000 rw-p 00000000 00:00 0
7f3b4c658000-7f3b4c679000 r-xp 00000000 09:7f 1050557 /usr/lib64/ld-2.22.so
7f3b4c84c000-7f3b4c850000 rw-p 00000000 00:00 0
7f3b4c877000-7f3b4c878000 rw-p 00000000 00:00 0
7f3b4c878000-7f3b4c879000 r--p 00020000 09:7f 1050557 /usr/lib64/ld-2.22.so
7f3b4c879000-7f3b4c87a000 rw-p 00021000 09:7f 1050557 /usr/lib64/ld-2.22.so
7f3b4c87a000-7f3b4c87c000 rw-p 00000000 00:00 0
7f3b4c87c000-7f3b4c87d000 rw-p 00000000 00:00 0
7fffa12a4000-7fffa12c5000 rw-p 00000000 00:00 0 [stack]
7fffa1398000-7fffa139a000 r--p 00000000 00:00 0 [vvar]
7fffa139a000-7fffa139c000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
Abortado (imagem do núcleo gravada)


Como a mensagem mesmo diz, é como se ocorresse uma dupla liberação de memória, o valgrind detecta isso(como a mensagem é muito grande nem vou passar, mas basta saber que ele detecta).

Descomentando a linha 4 do código vai sobrecarregar o operador de atribuição e o problema estará resolvido. Primeira vez que me deparo com uma situação desses e por causa disso vou ter que mudar algumas coisas dentro das minhas classes. Tem alguma explicação?

Achei que numa situação como esse seria apenas necessário e bastaria o construtor o Teste(const Teste &teste), mas pelo visto é necessário também sobrecarregar o operador de atribuição(=).

Obrigado. (Está sendo muito produtivo)


9. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 04/12/2015 - 19:57h

Eu não tenho como testar agora, mas eu lembro de uma mensagem neste fórum dizendo que o valgrind tinha problemas com threads.

EDIT: Pesquisando, o problema não era do valgrind com threads, mas alguma coisa com o valgrind de modo geral, que eu não consegui reproduzir na época em nenhuma circunstância. Por sinal, tinha sido um tópico aberto por você.


10. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

Uilian Ries
uilianries

(usa Linux Mint)

Enviado em 04/12/2015 - 20:00h


Paulo, concordo plenamente com a sua resposta. Respondeu exatamente o que foi perguntado, de forma clara.

Quando ao scoped_lock, foi engano meu.
O padrão C++11 prevê std::unique_lock, o tal scoped_lock pertence a Boost e significa:

typedef unique_lock<mutex> scoped_lock;


std::unique_lock e std::lock_guard são a mesma coisa, porém, std::unique_lock possui mais recursos.
Ambas utilizam o modelo de RAII para um mutex, porém std::unique_lock pode chamar unlock/lock, swap, try_lock, além de permitir ser construído ser chamar lock.


#include <mutex>
void consumer_thread()
{
std::unique_lock<std::mutex> lock(mutex);
auto task = queue.pop();
do_something(); // Pode lançar execeção
lock.unlock();

task();
}


std::lock_guard é um wrapper mais simples, ele somente irá chamar o lock na construção e unlock na destruição, nada além disso.


#include <mutex>
void consumer_thread()
{
bar* b = nullptr;
{
std::lock_guar<std::mutex> lock(mutex);
b = queue.pop();
}
b->task();
}



11. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

Paulo
paulo1205

(usa Ubuntu)

Enviado em 04/12/2015 - 20:08h

Quanto ao double free, até que é bem evidente (não sei como não vi antes!): você está copiando um ponteiro entre dois objetos, e liberando-o no destrutor de cada um deles. É evidente que haverá uma dupla liberação do mesmo ponteiro, o que é um erro.

Você poderia usar std::shared_ptr para proteger sua mutex.


12. Re: [Threads Concorrentes] Uso simples de MUTEX é seguro e não gera DEADLOCKs? [RESOLVIDO]

M.
XProtoman

(usa Fedora)

Enviado em 04/12/2015 - 20:11h

paulo1205 escreveu:

Eu não tenho como testar agora, mas eu lembro de uma mensagem neste fórum dizendo que o valgrind tinha problemas com threads.

EDIT: Pesquisando, o problema não era do valgrind com threads, mas alguma coisa com o valgrind de modo geral, que eu não consegui reproduzir na época em nenhuma circunstância. Por sinal, tinha sido um tópico aberto por você.

Boa noite Paulo, tudo bom? Lembro de você naquele post. :D

O código não tem threads por enquanto e ele rodou fora do valgrind(aquelas linhas eram fora dele), o mutex é alocado dinamicamente, decidi deixar o código mais enxuto e usar std::string em vez de std::mutex(para descartar ser um problema do mutex), removi as operações e ele apresenta o mesmo erro a não ser que você faça sobrecarga do operador de atribuição, segue:


#include <iostream>
#include <string>

//#define RESOLVER_PROBLEMA 1

class Teste
{
private:
std::string *string;
public:
Teste();
Teste(const Teste &teste);
~Teste();
#ifdef RESOLVER_PROBLEMA
void operator = (const Teste &teste);
#endif
};

Teste::Teste()
{
this->string = new std::string();
}

Teste::Teste(const Teste &teste): Teste()
{
}

Teste::~Teste()
{
delete this->string;
this->string = NULL;
}

#ifdef RESOLVER_PROBLEMA
void Teste::operator = (const Teste &teste)
{
}
#endif

int main()
{
Teste teste;
for ( size_t posicao = 0, posicao_maximo = 1000000;
posicao_maximo > posicao;
posicao++ )
{
teste = Teste();
}
return 0;
}


Parece ser uma característica da linguagem(C++), estou consertando minhas classes para escapar desse problema. Para solucionar é só descomentar a linha 4.



01 02



Patrocínio

Site hospedado pelo provedor RedeHost.
Linux banner

Destaques

Artigos

Dicas

Tópicos

Top 10 do mês

Scripts