AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / coding / Perguntas / 79555961
Accepted
RedGreenBlue123
RedGreenBlue123
Asked: 2025-04-05 01:31:02 +0800 CST2025-04-05 01:31:02 +0800 CST 2025-04-05 01:31:02 +0800 CST

C11 atômico: Como uma carga relaxada interage com um armazenamento de liberação na mesma variável?

  • 772

Contexto: Tenho escrito um programa multithread que usa atomics extensivamente. Percebi que esses atomics são muito lentos, especialmente no ARM, porque o compilador inseriu muitas fences, às vezes até dentro de loops. Então, quero eliminar as desnecessárias usando ordens de memória.

Eu tropecei neste caso, mas não tenho certeza se é seguro usar uma carga relaxada ou não. Veja este exemplo simples de leitura de parâmetros:

typedef struct {
    big_struct Data;
    _Atomic bool bDataReadDone;
} worker_thread_parameter;

static int WorkerThreadFunction(void* Parameter) {
    // Read Data
    worker_thread_parameter* pWorkerParameter = Parameter;
    big_struct Data = pWorkerParameter->Data;

    // Notify that reading Data is done
    // Use release store to ensure Data is read before this.
    atomic_store_explicit(&pWorkerParameter->bDataReadDone, true, memory_order_release);
        
    // Do something with Data
}

int main() {
    thrd_t aWorkerThread[8];
    for (size_t i = 0; i < 8; ++i) {
        worker_thread_parameter WorkerParameter = { /* Data = something */, false };
        thrd_create(&aWorkerThread[i], WorkerThreadFunction, &WorkerParameter);

        // Wait for Data to be read
        // Use relaxed load because this thread doesn't read Data anymore,
        // so we don't need to synchronize with the flag.
        while (!atomic_load_explicit(&WorkerParameter.bDataReadDone, memory_order_relaxed));
    }
}

Ou este exemplo:

// Initialized before the threads are started
_Atomic bool bUsingData = true;
big_struct* pData = malloc(sizeof(*pData));

static int WorkerThread() {
    Use(pData);

    // Notify the cleaner thread to free Data
    // Use release store to ensure Data is used before this.
    atomic_store_explicit(&bUsingData, false, memory_order_release);
}

static int CleanerThread() {
    // Use relaxed load because this thread doesn't read Data anymore,
    // so we don't need to synchronize with the flag.
    while (atomic_load_explicit(bUsingData, memory_order_relaxed));
    free(pData);
}

E este exemplo:

_Atomic int X = 0;
_Atomic int Y = 0;

// Thread 1

atomic_store_explicit(&X, 99, memory_order_relaxed);
atomic_store_explicit(&Y, 1, memory_order_release);

// Thread 2

if (atomic_load_explicit(&Y, memory_order_relaxed)) {
    atomic_store_explicit(&X, 100, memory_order_relaxed);
    printf("%i", atomic_load_explicit(&X, memory_order_relaxed));
}

// Does thread 2 always prints 100?
c
  • 2 2 respostas
  • 85 Views

2 respostas

  • Voted
  1. Best Answer
    Peter Cordes
    2025-04-05T03:05:37+08:002025-04-05T03:05:37+08:00

    Suas relaxedcargas não criam um happen-before com o release-store, então, no modelo de memória do padrão ISO para seu último exemplo, o X=100 (relaxed)store poderia terminar antes do X=99 (relaxed)store na Xordem de modificação de , com o reload capaz de ver qualquer valor dependendo do tempo.


    Não acho que isso seja possível em hardware real, no entanto (ou seja, o exemplo 2 só poderia imprimir 100, ou nada se o ifnão for usado). Porque os armazenamentos não podem se comprometer com o cache coerente até que não sejam especulativos. Em uma CPU exec OoO, isso significa que todas as instruções anteriores já devem ter se aposentado do ROB (Reorder Buffer).
    As cargas podem se aposentar assim que forem conhecidas como não defeituosas (em ISAs com modelos de memória fracos como ARM, mas não x86), mas a ramificação para implementar o iftem que esperar pelo resultado da carga antes de poder executar para verificar a previsão. Em uma CPU em ordem, o exec especulativo após uma ramificação não aconteceria em primeiro lugar.

    Então, em hardware real, acho que uma releaseloja para ordenar as lojas umas em relação às outras é suficiente para seu último exemplo.

    É claro que ele X.store(100, relaxed)pode ser executado localmente (escrevendo seu armazenamento no buffer de armazenamento ), e o recarregamento pelo mesmo thread pode ler esse valor do buffer de armazenamento (encaminhamento de armazenamento) conforme a CPU especula no ifcorpo, mas um armazenamento no buffer de armazenamento deste núcleo está mais tarde na ordem de modificação do que qualquer outro que já tenha sido confirmado no cache.

    Não tenho certeza se o encaminhamento de armazenamento cross-SMT pode causar efeitos interessantes aqui (por exemplo, no POWER ou NVidia ARMv7). Não creio; mesmo que o thread 2 esteja vendo valores X e Y por meio do encaminhamento de armazenamento de outro núcleo lógico, acho que esse núcleo lógico ainda tem que tratar seus próprios armazenamentos como Xmais novos do que os de outros núcleos. Caso contrário, acquire não funcionaria.


    Curiosidade: o modelo de memória do Linux é baseado em barreiras explícitas, como smp_rmb()ser uma barreira de leitura de memória para memória compartilhada entre núcleos. (Ao contrário de rmb()ser uma barreira de leitura de memória, inclusive para acessos de E/S.)

    https://github.com/torvalds/linux/blob/master/tools/memory-model/Documentation/control-dependencies.txt documenta que uma dependência de controle (como a sua if()) garante a ordenação do LoadStore quando a carga é a condição de ramificação, como no seu caso. O modelo de memória do Linux é baseado em coisas que são verdadeiras em todos os ISAs com os quais ele se importa.

    No entanto, as lojas não são especuladas. Isso significa que a ordenação é (geralmente) garantida para dependências de controle load-store, como no exemplo a seguir:

    Acredito que o "geralmente" seja por causa de um exemplo possível mais complexo, não por causa de algumas máquinas que não garantem esse exemplo, mas qualquer um que planeje depender dele deve ler o documento inteiro.

    Então, sim, tenho quase certeza de que o conjunto para o qual seu C é compilado será seguro, mas isso não é garantido na máquina abstrata C.

    • 2
  2. Bernd Benner
    2025-04-06T19:23:03+08:002025-04-06T19:23:03+08:00

    A implementação para __atomic .. é específica da arquitetura.

    Em x86/x86_64, toda a ordenação de memória criará o mesmo código (com um prefixo de bloqueio). Em ARM/ARM64, todas as implementações são diferentes. Minha experiência é que o __ATOMIC_SEQ_CST mais forte é o melhor. (Não preciso de instruções atômicas que podem falhar!) Tenho uma implementação de lista sem bloqueio segura para simultaneidade, funcionando corretamente com apenas __ATOMIC_SEQ_CST para __atomic_compare_exchange. Você também pode tentar usar -mtune=cortex-XX -mno-outline-atomicsfor gcc para obter o melhor desempenho de código atômico .

    • -1

relate perguntas

  • Multiplicação mais rápida que *

  • Usando uma macro para comprimento de string no especificador de formato scanf () em C

  • Como você pode definir o tipo de dados de #define para long double?

  • Ponteiros const incompatíveis

  • Mudança de cor não gradual no OpenGL

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Reformatar números, inserindo separadores em posições fixas

    • 6 respostas
  • Marko Smith

    Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não?

    • 2 respostas
  • Marko Smith

    Problema com extensão desinstalada automaticamente do VScode (tema Material)

    • 2 respostas
  • Marko Smith

    Vue 3: Erro na criação "Identificador esperado, mas encontrado 'import'" [duplicado]

    • 1 respostas
  • Marko Smith

    Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores?

    • 1 respostas
  • Marko Smith

    Como faço para corrigir um erro MODULE_NOT_FOUND para um módulo que não importei manualmente?

    • 6 respostas
  • Marko Smith

    `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso?

    • 3 respostas
  • Marko Smith

    Um programa vazio que não faz nada em C++ precisa de um heap de 204 KB, mas não em C

    • 1 respostas
  • Marko Smith

    PowerBI atualmente quebrado com BigQuery: problema de driver Simba com atualização do Windows

    • 2 respostas
  • Marko Smith

    AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos

    • 1 respostas
  • Martin Hope
    Fantastic Mr Fox Somente o tipo copiável não é aceito na implementação std::vector do MSVC 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant Encontre o próximo dia da semana usando o cronógrafo 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor O inicializador de membro do construtor pode incluir a inicialização de outro membro? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský Por que os conceitos do C++20 causam erros de restrição cíclica, enquanto o SFINAE antigo não? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul O C++20 mudou para permitir a conversão de `type(&)[N]` de matriz de limites conhecidos para `type(&)[]` de matriz de limites desconhecidos? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann Como/por que {2,3,10} e {x,3,10} com x=2 são ordenados de forma diferente? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller O ponto e vírgula agora é opcional em condicionais bash com [[ .. ]] na versão 5.2? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench Por que um traço duplo (--) faz com que esta cláusula MariaDB seja avaliada como verdadeira? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng Por que `dict(id=1, **{'id': 2})` às vezes gera `KeyError: 'id'` em vez de um TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob: MobileAds.initialize() - "java.lang.Integer não pode ser convertido em java.lang.String" para alguns dispositivos 2024-03-20 03:12:31 +0800 CST

Hot tag

python javascript c++ c# java typescript sql reactjs html

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve