【问题标题】:Multithreading: waiting for a thread to finish so I can retry a deadlock多线程:等待线程完成,以便我可以重试死锁
【发布时间】:2021-05-27 15:52:15
【问题描述】:

我有 2 个线程,我必须让成功的线程等待,而死锁的线程重试。当前代码只适用于第一个循环,因为成功的线程退出了。

我尝试使用 Thread.Join() 没有成功。

如何让 'success' 为 'true' 的线程等待死锁线程完成?

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Thread thread1 = new Thread(new ThreadStart(procedure1));
            Thread thread2 = new Thread(new ThreadStart(procedure2));

            thread1.Start();
            thread2.Start();
        }


        private void procedure1()
        {
            Console.WriteLine("thread 1");
            bool success = false;
            int retryCount = 1;
            while ((retryCount <= 3) && success == false)
            {
                try
                {
                    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                    {
                        SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure1"), connection);
                        connection.Open();
                        cmd.ExecuteNonQuery();
                        success = true;
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 1205)
                    {
                        if (retryCount == 1) Console.WriteLine("Thread 1 deadlocked");
                        Console.WriteLine("Thread 1 deadlock retry number " + retryCount.ToString());
                        retryCount++;
                    }
                    else
                        Console.WriteLine(ex.Message);
                }
            }
        }

        private void procedure2()
        {
            Console.WriteLine("thread 2");
            bool success = false;
            int retryCount = 1;
            while ((retryCount <= 3) && success == false)
            {
                try
                {
                    using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                    {
                        SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure2"), connection);
                        connection.Open();
                        cmd.ExecuteNonQuery();
                        success = true;
                    }
                }
                catch (SqlException ex)
                {
                    if (ex.Number == 1205)
                    {
                        if (retryCount == 1) Console.WriteLine("Thread 2 deadlocked");
                        Console.WriteLine("Thread 2 deadlock retry number " + retryCount.ToString());
                        retryCount++;
                    }
                    else
                        Console.WriteLine(ex.Message);
                }
            }
        }
    }

用于死锁的 SQL 过程:

CREATE OR ALTER PROCEDURE deadlockP1
AS
BEGIN
    BEGIN TRAN;
        UPDATE Employee SET position = 'handler' WHERE employee_code = 4037;
        WAITFOR DELAY '00:00:05';
        UPDATE Shop SET shop_name = 'TITANUS' WHERE shop_code = 2019;
    COMMIT TRAN;    
END

CREATE OR ALTER PROCEDURE deadlockP2
AS
BEGIN
    BEGIN TRAN;
        UPDATE Shop SET shop_name = 'TITANUS' WHERE shop_code = 2019;
        WAITFOR DELAY '00:00:05';
        UPDATE Employee SET position = 'manager' WHERE employee_code = 4037;
    COMMIT TRAN;    
END

【问题讨论】:

  • join 是您问题的解决方案。可以添加您的代码段以显示您是如何使用 join 的吗?
  • “我有 2 个线程,我必须让成功的线程等待,而死锁的一个重试。” - 你呢?
  • @Fildor 我也对他们必须互相等待的要求感到困惑。也许还有其他未列出的实现必须同步
  • 要求是我必须多次重试死锁线程(在我的代码中为 3 次),直到它被认为完成('retryCount 为 '3')。
  • 为什么这个要求意味着其他线程在等待?

标签: c# sql .net multithreading forms


【解决方案1】:

如何让 'success' 为 'true' 的线程等待死锁线程完成?

如果所有线程必须在相似的时间完成并相互等待,请考虑使用Barrier。否则我建议使用Join(见下文)。

一组任务通过一系列阶段进行合作,其中组中的每个任务都表示它已在给定阶段到达障碍,并隐含地等待所有其他任务到达。同一个屏障可以用于多个阶段。

当你使用屏障时,它基本上意味着你分配给屏障的所有线程都必须到达一个“检查点”才能继续。

下面是一个简单的例子:

public partial class Form1 : Form
{
    // create a barrier with 2 slots
    private Barrier barrier = new Barrier(2);

    private void procedure1()
    {
        Console.WriteLine("thread 1");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure1"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 1 deadlocked");
                    Console.WriteLine("Thread 1 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for all other tasks in barrier to get to this checkpoint before returning
        barrier.SignalAndWait();
    }

    private void procedure2()
    {
        Console.WriteLine("thread 2");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure2"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 2 deadlocked");
                    Console.WriteLine("Thread 2 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for all other tasks in barrier to get to this checkpoint before returning
        barrier.SignalAndWait();
    }
}

需要注意的是,虽然可以使用Task.WhenAll(async) 和Task.WaitAll(sync) 来实现类似的效果,但它们与Join 一起更容易实现(但你说你不想使用/无法使用连接)。

如果您想重新考虑使用Join,请考虑将您要等待的线程的引用传递给另一个线程。 Join 阻塞当前线程(等待),而它调用 Join 的线程的 instance 完成。

简而言之,Join 的工作原理如下,请确保不要让两个线程试图相互等待。您将等待他们很长时间才能弄清楚。

void ThreadOne(Thread other)
{
    // do work
    // ...
    
    // wait for other to finish
    other?.Join();
}

void ThreadTwo()
{
    // do work
    // ...
}

当您使用Joins 时,您会遇到两个任务必须相互等待的问题。在您的示例中,您希望在当前线程完成工作时等待(加入),但另一个没有。我们如何知道其他线程何时完成它的工作?

我们应该使用sentinel 值来通知其他线程工作已经完成或仍需要完成。

我们可以为标记值使用很多东西,从封装变量、类变量甚至WaitHandles。

如果我们使用标记值,我们可以使用Join,方法是向每个线程传递一个相互引用。

这可能是这样的:

public partial class Form1 : Form
{
    // create sentinels
    bool thread1Status = false;
    bool thread2Status = false;

    // create a spot to store the threads so they can reference each other
    Thread thread1;
    Thread thread2;


    private void Form1_Load(object sender, EventArgs e)
    {
        thread1 = new Thread(new ThreadStart(procedure1));
        thread2 = new Thread(new ThreadStart(procedure2));

        thread1.Start();
        thread2.Start();
    }

    private void procedure1()
    {
        Console.WriteLine("thread 1");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure1"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;
                    
                    // notify other thread that we finished work
                    thread1Status = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 1 deadlocked");
                    Console.WriteLine("Thread 1 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for the other thread to finish work if it hasn't
        if(success && thread2Status == false)
        {
             thread2?.Join();
        }
    }

    private void procedure2(Thread other)
    {
        Console.WriteLine("thread 2");
        bool success = false;
        int retryCount = 1;
        while ((retryCount <= 3) && success == false)
        {
            try
            {
                using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["connection1"].ConnectionString))
                {
                    SqlCommand cmd = new SqlCommand(ConfigurationManager.AppSettings.Get("execProcedure2"), connection);
                    connection.Open();
                    cmd.ExecuteNonQuery();
                    success = true;

                    // notify other thread that we finished work
                    thread2Status = true;
                }
            }
            catch (SqlException ex)
            {
                if (ex.Number == 1205)
                {
                    if (retryCount == 1) Console.WriteLine("Thread 2 deadlocked");
                    Console.WriteLine("Thread 2 deadlock retry number " + retryCount.ToString());
                    retryCount++;
                }
                else
                    Console.WriteLine(ex.Message);
            }
        }

        // wait for the other thread to finish work if it hasn't
        if(success && thread1Status == false)
        {
             thread1?.Join();
        }
    }
}

【讨论】:

  • 谢谢!甚至没有考虑过使用哨兵...
猜你喜欢
  • 2022-01-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多