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 / 79237404
Accepted
Minimus Heximus
Minimus Heximus
Asked: 2024-11-29 22:17:13 +0800 CST2024-11-29 22:17:13 +0800 CST 2024-11-29 22:17:13 +0800 CST

Por que a saída deste programa multithread varia a menos que Thread.Join seja usado ou Thread.Sleep seja removido?

  • 772

Tenho o seguinte programa, onde gero 1000 threads para incrementar uma variável compartilhada a e então faço cada thread dormir por 1 segundo:

using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Runtime.CompilerServices;
using System.Threading;

class Program
{

    static volatile int a = 0;
    static void Main()
    {
        List<Thread> threads = new List<Thread>();
        for (int i = 0; i < 1000; i++)
        {
            var t = new Thread(() =>
            {
                a++;
                Thread.Sleep(1000);
            });
            t.Start();
            threads.Add(t);
        }
        // foreach(Thread t in threads) t.Join();
        Thread.Sleep(60000);
        Console.WriteLine(a);
        Console.ReadKey();
    }
}

Problema:

Quando executo este código com a linha Thread.Sleep(1000) incluída e t.Join() comentado, a saída de Console.WriteLine(a) é menor que 1000, mesmo que eu aguarde 60 segundos para que os threads terminem.

Se eu descomentar o loop t.Join() ou comentar a linha Thread.Sleep(1000), a saída será consistentemente 1000.

Questões:

Por que a saída varia quando Thread.Sleep(1000) está presente e t.Join() está comentado?

Por que a saída se torna consistente (sempre 1000) quando t.Join() é usado ou Thread.Sleep(1000) é removido?

Eu apreciaria uma explicação do que está acontecendo aqui em termos de comportamento de thread e sincronização. Obrigado!

c#
  • 2 2 respostas
  • 64 Views

2 respostas

  • Voted
  1. Best Answer
    Guru Stron
    2024-11-29T22:37:45+08:002024-11-29T22:37:45+08:00

    TBH não foi capaz de reproduzir o efeito foreach(Thread t in threads) t.Join();vs Thread.Sleep- ambos consistentemente (em um número relativamente pequeno de tentativas, no entanto) dão a soma "esperada" (talvez haja alguma diferença de configuração que faça com que nem todos os threads consigam completar, embora isso Thread.Sleep(60000)seja muito duvidoso). Eu sugeriria alterar o código para:

    var t = new Thread(() =>
    {
        Thread.Sleep(1000);   
        a++;
    });
    

    ou seja, altere a ordem das operações, então o foreach(Thread t in threads) t.Join();truque vai parar de funcionar (pelo menos "na minha máquina"). O fato de que foi uma mera coincidência, eu diria que relacionado ao fato de que a++é uma operação relativamente rápida e, como você está iniciando o thread antes de adicioná-lo à lista e criar o próximo, ele tem tempo suficiente para ser concluído.

    Alterar a ordem de suspensão e incremento traz (pelo menos para mim) o problema principal à tona: incremento ( a++) não é atômico , e usá-lo em contexto multithread torna seu programa inválido . Você deve usar alguma sincronização ou versão atômica da operação (se houver).

    Por exemplo, Interlocked.Incrementtornará seu programa correto:

    var t = new Thread(() =>
    {
        Thread.Sleep(1000);   
        Interlocked.Increment(ref a);
    });
    

    Eu também recomendo conferir:

    1. Threading em C# por Joseph Albahari - um pouco datado por conceitos ainda relevantes
    2. The Deadlock Empire - o jogo expondo algumas armadilhas de atomicidade e sincronização (via .NET)
    • 4
  2. JonasH
    2024-11-29T22:40:26+08:002024-11-29T22:40:26+08:00

    Por que a saída varia quando Thread.Sleep(1000) está presente e t.Join() está comentado?

    O SO pode agendar threads sempre que desejar. Então, é provável que nem todas as threads tenham tido a chance de rodar. Como o SO não fornece nenhuma garantia de tempo específica, você não pode confiar apenas em esperar para garantir que as threads rodaram.

    Por que a saída se torna consistente (sempre 1000) quando t.Join()

    Join bloqueia a execução do thread atual até que o thread de junção tenha sido concluído. Então, se você juntar todos os threads, você pode ter certeza de que todos os threads concluíram a execução.

    Mas isso não significa que aseja garantido que seja 1000. É possível que o primeiro thread:

    1. Leia acomo0
    2. Seja preemptado pelo sistema operacional
    3. Retomar a execução após a conclusão de todos os outros threads
    4. Incremento apara1
    5. Escreva 1de memória

    Resultar em uma saída de 1 volatilenão impede isso, você precisaria de um bloqueio, ou Interlocked.Increment para incrementar uma variável com segurança.

    Multithreading é difícil , e você não pode usar testes como este para verificar se um comportamento está correto. Um teste pode ser bem-sucedido um milhão de vezes, mas falhar em outro computador, ou quando há uma carga alta, ou quando há lua cheia. Na melhor das hipóteses, você pode verificar se um comportamento está incorreto . Você precisa saber os perigos e como resolvê-los. As melhores práticas de threading gerenciado são um bom começo, mas há muitos outros bons artigos sobre segurança de thread.

    • 3

relate perguntas

  • Polly DecorrelatedJitterBackoffV2 - como calcular o tempo máximo necessário para concluir todas as novas tentativas?

  • Wpf. Role o DataGrid dentro do ScrollViewer

  • A pontuação que ganhei na página do jogo com .NET MAUI MVVM não é visível em outras páginas. Como posso manter os dados de pontuação no dispositivo local

  • Use a hierarquia TreeView com HierarchicalDataTemplate de dentro de um DataTemplate

  • Como posso melhorar essa interface de validação no .NET?

Sidebar

Stats

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

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

    • 1 respostas
  • Marko Smith

    Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle?

    • 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

    Quando devo usar um std::inplace_vector em vez de um std::vector?

    • 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
  • Marko Smith

    Estou tentando fazer o jogo pacman usando apenas o módulo Turtle Random e Math

    • 1 respostas
  • Martin Hope
    Aleksandr Dubinsky Por que a correspondência de padrões com o switch no InetAddress falha com 'não cobre todos os valores de entrada possíveis'? 2024-12-23 06:56:21 +0800 CST
  • Martin Hope
    Phillip Borge Por que esse código Java simples e pequeno roda 30x mais rápido em todas as JVMs Graal, mas não em nenhuma JVM Oracle? 2024-12-12 20:46:46 +0800 CST
  • Martin Hope
    Oodini Qual é o propósito de `enum class` com um tipo subjacente especificado, mas sem enumeradores? 2024-12-12 06:27:11 +0800 CST
  • Martin Hope
    sleeptightAnsiC `(expression, lvalue) = rvalue` é uma atribuição válida em C ou C++? Por que alguns compiladores aceitam/rejeitam isso? 2024-11-09 07:18:53 +0800 CST
  • Martin Hope
    The Mad Gamer Quando devo usar um std::inplace_vector em vez de um std::vector? 2024-10-29 23:01:00 +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
  • Martin Hope
    MarkB Por que o GCC gera código que executa condicionalmente uma implementação SIMD? 2024-02-17 06:17:14 +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