C#线程--5.0之前时代(二)、线程的同步

线程同步

说明:接上一篇,注意分享线程同步的必要性和线程同步的方法。

一、什么是线程同步:

在同一时间只允许一个线程访问资源的情况称为线程同步。

二、为什么需要线程同步:

  • 避免竞争条件;
  • 确保线程安全;(如果两个线程同时访问一个资源并对那个资源做修改,就不安全了)

现在的计算机变得越来越多核,每一个CPU可以独立工作,但是对于内存和外部资源、数据库的访问却可能因为不同线程的访问使数据产生异常,常见的例子就是银行的转账的例子不再赘述。

三、线程同步的方法:

  • 同步代码中重要的部分;
  • 使对象不可改变;
  • 使用线程安全包装器;

    注意:局部变量、方法参数和返回值是放在堆栈中的,本身是线程安全的。

四、线程不安全的演示:

背景:在数据库的user_blance表插入两条数据,两人的balance值都为1000.00,整个user_balance表的balance总值为2000.00

        static string connectionStr = "Server=127.0.0.1;Port=3306;Stmt=;Database=exe_dev; User=root;Password=123456";
        public static void UnSafeThread() {
            Thread ThreadOne = new Thread(new ThreadStart(DrawMoney));
            ThreadOne.Name = "A001";

            Thread ThreadTwo = new Thread(new ThreadStart(DrawMoney));
            ThreadTwo.Name = "A002";

            ThreadOne.Start();
            ThreadTwo.Start();
        }

        private static void DoDrawMoney()
        {
            Random random = new Random();
            int money = random.Next(100);

            string userId = Thread.CurrentThread.Name;
            string selectSql = "select balance from user_balance where user_id=@UserId";
            string updateSql = "update user_balance set balance=@Balance+@Money where user_id=@UserId";
            string updateSql2 = "update user_balance set balance=@Balance-@Money where user_id<>@UserId";
            using (MySqlConnection conn= MySqlConnectionHelper.OpenConnection(connectionStr))
            {
                var balance = conn.ExecuteScalar(selectSql, new { UserId = userId });
                if (balance != null)
                {
                    conn.Execute(updateSql, new { Money = money, Balance=balance, UserId = userId });
                    conn.Execute(updateSql2, new { Money = money, Balance = balance, UserId = userId });
                }
            }
        }

        private static void DrawMoney() {
            for (int i = 0; i < 100; i++)
            {
                DoDrawMoney();
            }
        }

运行结果:

程序中有三条线程在跑:两条支线程,一条主线程,主线程负责统计钱的总数,两条支线程模拟两个人赚钱,赚过来赚过去,哈哈哈,依据查询成果可以看到,钱的总数原本是2000.00,但是之后开始减少。当然上面的异常也可以通过加事务解决,或者改变sql的实现方式balance=balance+money,不过这个不是我们讨论的重点,不展开。

 五、线程同步:
1、MethodImplAttribute:同步方法

  MethodImplAttribute是一个属性,它用来告诉CLR方法是如何实现的,MethodImplAttribute的一个构造函数把MethodImplOptions的枚举值作为参数,MethodImplOptions的枚举值Synchronized告诉CLR,这个方法该一次性只能在一个线程上执行。 静态方法在类型上锁定,而实例方法在实例上锁定。 只有一个线程可在任意实例函数中执行,且只有一个线程可在任意类的静态函数中执行。

使用方式:在需要同步的方法上添加属性

[MethodImpl(MethodImplOptions.Synchronized)]
        [MethodImpl(MethodImplOptions.Synchronized)]
        private static void DoDrawMoney()
        {
            Random random = new Random();
            int money = random.Next(100);

            string userId = Thread.CurrentThread.Name;
            string selectSql = "select balance from user_balance where user_id=@UserId";
            string updateSql = "update user_balance set balance=@Balance+@Money where user_id=@UserId";
            string updateSql2 = "update user_balance set balance=balance-@Money where user_id<>@UserId";
            using (MySqlConnection conn= MySqlConnectionHelper.OpenConnection(connectionStr))
            {
                var balance = conn.ExecuteScalar(selectSql, new { UserId = userId });
                if (balance != null)
                {
                    conn.Execute(updateSql, new { Money = money, Balance=balance, UserId = userId });
                    conn.Execute(updateSql2, new { Money = money, Balance = balance, UserId = userId });
                }
            }
        }

持续更新中...

猜你喜欢

转载自www.cnblogs.com/heisehenbai/p/9960978.html