Por favor, explique a partir das perspectivas do Linux, Windows?
Estou programando em c #, esses dois termos fariam diferença. Por favor, poste o máximo que puder, com exemplos e coisas assim ....
obrigado
Por favor, explique a partir das perspectivas do Linux, Windows?
Estou programando em c #, esses dois termos fariam diferença. Por favor, poste o máximo que puder, com exemplos e coisas assim ....
obrigado
Respostas:
Para Windows, as seções críticas são mais leves que as mutexes.
Mutexes podem ser compartilhados entre processos, mas sempre resultam em uma chamada do sistema ao kernel que possui alguma sobrecarga.
As seções críticas podem ser usadas apenas dentro de um processo, mas têm a vantagem de mudar apenas para o modo kernel no caso de contenção - aquisições não contidas, que devem ser o caso comum, são incrivelmente rápidas. No caso de contenção, eles entram no kernel para aguardar alguma primitiva de sincronização (como um evento ou semáforo).
Eu escrevi um aplicativo de amostra rápida que compara o tempo entre os dois. No meu sistema, para 1.000.000 de aquisições e lançamentos independentes, um mutex leva mais de um segundo. Uma seção crítica leva ~ 50 ms para 1.000.000 de aquisições.
Aqui está o código de teste, eu executei isso e obtive resultados semelhantes se o mutex for o primeiro ou o segundo, então não estamos vendo nenhum outro efeito.
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
}
QueryPerformanceCounter(&end);
int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
}
QueryPerformanceCounter(&end);
int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
printf("Mutex: %d CritSec: %d\n", totalTime, totalTimeCS);
De uma perspectiva teórica, uma seção crítica é um pedaço de código que não deve ser executado por vários encadeamentos de uma só vez, porque o código acessa recursos compartilhados.
Um mutex é um algoritmo (e algumas vezes o nome de uma estrutura de dados) usado para proteger seções críticas.
Semáforos e monitores são implementações comuns de um mutex.
Na prática, existem muitas implementações mutex disponíveis no Windows. Eles diferem principalmente como conseqüência de sua implementação por seu nível de bloqueio, escopos, custos e desempenho em diferentes níveis de contenção. Consulte CLR Inside Out - Usando simultaneidade para escalabilidade para obter um gráfico dos custos de diferentes implementações de mutex.
Primitivas de sincronização disponíveis.
A lock(object)
declaração é implementada usando um Monitor
- consulte MSDN para referência.
Nos últimos anos, muita pesquisa é feita sobre sincronização sem bloqueio . O objetivo é implementar algoritmos de maneira livre de bloqueio ou sem espera. Nesses algoritmos, um processo ajuda outros processos a finalizar seu trabalho, para que o processo possa finalmente terminar seu trabalho. Em conseqüência, um processo pode terminar seu trabalho mesmo quando outros processos, que tentaram executar algum trabalho, travam. Usando bloqueios, eles não liberariam seus bloqueios e impediriam a continuidade de outros processos.
Além das outras respostas, os seguintes detalhes são específicos para seções críticas no Windows:
InterlockedCompareExchange
operaçãoNo linux, acho que eles têm um "bloqueio de rotação" que serve a um propósito semelhante à seção crítica com uma contagem de rotação.
Seção crítica e Mutex não são específicos do sistema operacional, seus conceitos de multithreading / multiprocessing.
Seção crítica É um pedaço de código que só deve ser executado a qualquer momento (por exemplo, há 5 threads em execução simultaneamente e uma função chamada "critical_section_function" que atualiza uma matriz ... você não deseja todos os 5 threads atualizando a matriz de uma vez.Portanto, quando o programa estiver executando critical_section_function (), nenhum dos outros threads deve executar sua critical_section_function.
mutex * O mutex é uma maneira de implementar o código de seção crítica (pense nele como um token ... o thread deve ter posse dele para executar o critical_section_code)
Um mutex é um objeto que um thread pode adquirir, impedindo que outros threads o adquiram. É consultivo, não obrigatório; um encadeamento pode usar o recurso que o mutex representa sem adquiri-lo.
Uma seção crítica é um comprimento de código garantido pelo sistema operacional para não ser interrompido. No pseudo-código, seria como:
StartCriticalSection();
DoSomethingImportant();
DoSomeOtherImportantThing();
EndCriticalSection();
O Windows 'rápido' igual à seleção crítica no Linux seria um futex , que significa mutex rápido no espaço do usuário. A diferença entre um futex e um mutex é que, com um futex, o kernel somente se envolve quando a arbitragem é necessária, portanto, você economiza a sobrecarga de conversar com o kernel cada vez que o contador atômico é modificado. Isso pode economizar uma quantidade significativa de tempo negociando bloqueios em alguns aplicativos.
Um futex também pode ser compartilhado entre processos, usando os meios que você empregaria para compartilhar um mutex.
Infelizmente, os futexes podem ser muito difíceis de implementar (PDF). (Atualização de 2018, eles não são tão assustadores quanto em 2009).
Além disso, é praticamente o mesmo nas duas plataformas. Você está fazendo atualizações atômicas e controladas por token em uma estrutura compartilhada de uma maneira que (espero) não cause fome. O que resta é simplesmente o método de conseguir isso.
No Windows, uma seção crítica é local para o seu processo. Um mutex pode ser compartilhado / acessado entre processos. Basicamente, as seções críticas são muito mais baratas. Não é possível comentar especificamente sobre o Linux, mas em alguns sistemas eles são apenas aliases para a mesma coisa.
Apenas para adicionar meus 2 centavos, as Seções críticas são definidas como uma estrutura e as operações são realizadas no contexto do modo de usuário.
ntdll! _RTL_CRITICAL_SECTION + 0x000 DebugInfo: Ptr32 _RTL_CRITICAL_SECTION_DEBUG + 0x004 LockCount: Int4B + 0x008 RecursionCount: Int4B + 0x00c OwningThread: Ptr32 Vazio + 0x010 LockSemaphore: Ptr32 Vazio + 0x014 SpinCount: Uint4B
Enquanto mutex são objetos de kernel (ExMutantObjectType) criados no diretório de objetos do Windows. As operações do Mutex são implementadas principalmente no modo kernel. Por exemplo, ao criar um Mutex, você acaba chamando nt! NtCreateMutant no kernel.
Ótima resposta de Michael. Adicionei um terceiro teste para a classe mutex introduzida no C ++ 11. O resultado é um pouco interessante e ainda suporta o endosso original de objetos CRITICAL_SECTION para processos únicos.
mutex m;
HANDLE mutex = CreateMutex(NULL, FALSE, NULL);
CRITICAL_SECTION critSec;
InitializeCriticalSection(&critSec);
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
LARGE_INTEGER start, end;
// Force code into memory, so we don't see any effects of paging.
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
EnterCriticalSection(&critSec);
LeaveCriticalSection(&critSec);
}
QueryPerformanceCounter(&end);
int totalTimeCS = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
WaitForSingleObject(mutex, INFINITE);
ReleaseMutex(mutex);
}
QueryPerformanceCounter(&end);
int totalTime = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
// Force code into memory, so we don't see any effects of paging.
m.lock();
m.unlock();
QueryPerformanceCounter(&start);
for (int i = 0; i < 1000000; i++)
{
m.lock();
m.unlock();
}
QueryPerformanceCounter(&end);
int totalTimeM = (int)((end.QuadPart - start.QuadPart) * 1000 / freq.QuadPart);
printf("C++ Mutex: %d Mutex: %d CritSec: %d\n", totalTimeM, totalTime, totalTimeCS);
Meus resultados foram 217, 473 e 19 (observe que minha proporção de vezes nos últimos dois é comparável à de Michael, mas minha máquina é pelo menos quatro anos mais nova que a dele, para que você possa ver evidências de aumento de velocidade entre 2009 e 2013 , quando o XPS-8700 saiu). A nova classe mutex é duas vezes mais rápida que o Windows mutex, mas ainda menos que um décimo da velocidade do objeto Windows CRITICAL_SECTION. Observe que eu testei apenas o mutex não recursivo. Os objetos CRITICAL_SECTION são recursivos (um thread pode inseri-los repetidamente, desde que deixe o mesmo número de vezes).
As funções CA são chamadas reentrantes se usar apenas seus parâmetros reais.
As funções reentrantes podem ser chamadas por vários threads ao mesmo tempo.
Exemplo de função reentrante:
int reentrant_function (int a, int b)
{
int c;
c = a + b;
return c;
}
Exemplo de função não reentrante:
int result;
void non_reentrant_function (int a, int b)
{
int c;
c = a + b;
result = c;
}
A biblioteca padrão C strtok () não é reentrada e não pode ser usada por 2 ou mais threads ao mesmo tempo.
Alguns SDKs de plataforma vêm com a versão reentrante do strtok () chamada strtok_r ();
Enrico Migliore