Desculpe pela resposta tardia, mas acabei de encontrar a pergunta (perguntas, de fato). Também estou estudando concorrência e tentarei compartilhar algumas idéias com você.
Primeiro, vamos começar com consistência sequencial. Um modelo possui essa propriedade se as operações parecerem ter efeito na ordem do programa. Em outras palavras, a ordem na qual as linhas de código são executadas é a especificada no arquivo de origem. Esse pré-requisito é específico do encadeamento: operações que envolvem encadeamentos diferentes não são relacionadas à ordem do programa. Portanto, a propriedade concede o seguinte: operações emitidas pelo mesmo encadeamento são executadas na mesma ordem especificada pelo código-fonte do encadeamento. Operações emitidas por diferentes threads podem ocorrer em qualquer ordem. Esse pré-requisito pode parecer óbvio, mas nem sempre é o caso (otimizações do compilador podem alterar a ordem na qual as operações são emitidas, tornando a ordem do programa diferente da origem).
Seu exemplo está certo, mas deixe-me dar alguns outros. Considere este programa P1 (vou marcar cada linha para uma referência fácil):
int x = 1;
void ThreadA()
{
A1: x = x * 2;
A3: int a = x;
}
void ThreadB()
{
B2: x = 20;
}
Existe uma execução sequencial em que, no final, a = 40? Sim: B2, A1, A3.
B2 e A1 podem ser executados em qualquer ordem (eles pertencem a diferentes segmentos). A1 é executado antes de A3 (ordem do programa = ordem do código fonte).
Agora considere este programa P2:
int y = 1;
void ThreadA()
{
A2: y = 40;
}
void ThreadB()
{
B1: y = y / 2;
B3: int b = y;
}
Existe uma execução sequencial em que, no final, b = 20? Sim: A2, B1, B3.
E a composição? Vamos compor P1 e P2. Temos P1∘P2:
int x = 1;
int y = 1;
void ThreadA()
{
A1: x = x * 2;
A2: y = 40;
A3: int a = x;
}
void ThreadB()
{
B1: y = y / 2;
B2: x = 20;
B3: int b = y;
}
Se a consistência sequencial fosse composicional, deveria haver uma execução na qual, no final, a = 40 eb = 20. É esse o caso? Não. Vamos dar uma prova formal, oh, por que não pode ser o caso. Escreverei "o1 → o2" para dizer que a operação o1 deve ser executada antes da operação o2.
Em P1∘P2, devido à consistência sequencial, o seguinte deve ser válido: A1 -> A2 e B1 -> B2 (ordem do programa por thread). Para obter a = 40, também B2 -> A1 deve reter. Para obter b = 20 também A2 -> B1 deve se manter. Você pode ver a cadeia de precedência? A1 -> A2 -> B1 -> B2 -> A1 -> ...
P1 e P2 eram sequencialmente consistentes, mas sua composição P1∘P2 não era. A consistência sequencial não é composicional. O exemplo que você forneceu não foi suficientemente complicado para mostrar esse fato.
Agora, vamos considerar a consistência inativa. É difícil para mim explicar essa propriedade sem usar o paradigma orientado a objetos. De fato, a consistência inativa pode ser facilmente entendida em termos de chamadas de método pendentes aos objetos. No entanto, tentarei manter o paradigma imperativo (as chamadas de método pendentes tornam-se funções iniciadas, mas não concluídas, objetos tornam-se variáveis envolvidas em funções).
Uma chamada de função é composta por uma chamada e uma resposta. Uma função está pendente se tiver sido chamada por um encadeamento, mas ainda não retornou uma resposta a esse encadeamento. Um período de inatividade para uma variável é um intervalo de tempo em que não há chamadas de função pendentes (por qualquer encadeamento) operando nela. Um modelo possui a propriedade de consistência inativa se chamadas de função operando na mesma variável e separadas por um período de inatividade parecem ter efeito na ordem em tempo real (a especificada pelo código-fonte). Do ponto de vista inverso, a definição afirma que as operações na mesma variável executada por funções chamadas simultaneamente por threads diferentes (não separadas por quiescência) podem ocorrer em qualquer ordem.
Tentei por muitas horas projetar um exemplo significativo usando apenas operações de leitura e gravação para mostrar a diferença entre consistência inativa e sequencial, mas não obtive sucesso. Deixe-me usar outro exemplo envolvendo conjuntos. Vou usar um vetor de bits para acompanhar quais números inteiros (de 0 a 4) estão no conjunto:
int set[5] = {0, 0, 0, 0, 0}; // 0 if i-th item is absent, 1 otherwise
void add(int number) {
L1: set[number] = 1;
L2: temp foo = set[0];
}
void remove(int number) {
set[number] = 0;
}
int contains(int number) {
return set[number] == 1;
}
void ThreadA()
{
A1: add(1);
}
void ThreadB()
{
B1: add(2);
B2: remove(2);
B3: int res = contains(2);
}
Existe uma execução sequencial em que, no final, res = 1? Não: devido à consistência sequencial B1 -> B2 e B2 -> B3, então B1 -> B3. Portanto, remove (2) é sempre executado após add (2) e contém (2) sempre retornará 0.
Existe uma execução inativa em que, no final, res = 1? Sim! Considere esta execução:
O segmento A chama add (1) em A1.
O segmento A executa L1 (mas não L2). // como existe uma chamada pendente no conjunto, agora o Thread B está livre para executar B2 antes de B1 porque essas chamadas envolvem o conjunto também
O segmento B chama B2. // invocação + resposta
O segmento B chama B1. // invocação + resposta
O segmento A executa L2 e o add (1) responde. // agora há quiescência no set
O segmento B executa B3. // invocação + resposta
Agora res = 1.
Infelizmente, ainda não consigo responder à última pergunta sobre por que a consistência quieta é composicional: encontrei sua pergunta enquanto procurava essa resposta exata. Se eu inventar algo, eu o informarei.
Para uma boa leitura do tópico, dê uma olhada no capítulo 3.3 do livro "The Art of Multiprocessor Programming", de Herlihy e Shavit.
EDIT: Acabei de encontrar uma ótima página explicando por que a consistência quieta é composicional. E esta é outra leitura muito boa!
EDIT2: Corrigido pequeno erro no exemplo de composição sequencial de consistência.