• C# 模拟并发


    每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!

    当然,题外话说多了,咱进入正题!

    在处理大数据的时候,经常会发生并发,并发的情况发生后,会出现数据污读,从而产生脏数据。

    首先通过一段程序进行说明、<有兴趣的小伙伴可以复制粘贴这段程序>。

    项目背景:模拟大转盘抽奖程序。

    场下坐有近万名群众,他们在同一时刻同时抽奖,奖品分为一等奖:奔驰汽车10辆,二等奖:别克汽车20辆,三等奖:现代汽车30辆。(奖品信息存入数据库)

    奖品信息如下(数据库部分):

    create table JP_test
    (
    Id int identity(1,1),
    JpLeave int,--奖品等级
    JpName nvarchar(50),--奖品名称
    jpCount int,--奖品数量
    )

    insert into JP_test values(1,'奔驰汽车',10)
    insert into JP_test values(2,'别克汽车',20)
    insert into JP_test values(3,'现代汽车',30)

    update JP_test set jpCount=10 where JpLeave=1
    update JP_test set jpCount=20 where JpLeave=2
    update JP_test set jpCount=30 where JpLeave=3

    --以上SQL语句大家看懂表示没压力。在此不作讲解。

    程序思路:通过开辟线程进行模拟操作,一个线程代表一个群众,由于群众近万人,我们暂且开辟十个线程并通过FOR循环进行模拟!

    条件判断:当数据库中一等奖数据小于1时,不再进行一等奖的抽奖工作,也就是不再进行针对一等奖数量的减少工作。同理二等奖,三等奖。

    最后输出抽奖后的一等奖,二等奖,三等奖数量。

    按照我们的设计思路,很显然最后的答案应该为:一等奖,二等奖,三等奖数量都为0,真实情况是否如此?请看下面代码。

    模拟代码如下:

     class Program
        {
            static T_SQL imp = new T_SQL();//数据库操作类,这个很简单,大家可利用自己现有的类进行数据库操作。在此不作解释。
            /// <summary>
            /// 场景:模拟大转盘抽奖  X个人同时抽奖  直至奖品被抽完。
            /// </summary>
            static void Main(string[] args)
            {
                Task td1 = Task.Factory.StartNew(choujiang);
                Task td2 = Task.Factory.StartNew(choujiang);
                Task td3 = Task.Factory.StartNew(choujiang);
                Task td4 = Task.Factory.StartNew(choujiang);
                Task td5 = Task.Factory.StartNew(choujiang);
                Task td6 = Task.Factory.StartNew(choujiang);
                Task td7 = Task.Factory.StartNew(choujiang);
                Task td8 = Task.Factory.StartNew(choujiang);
                Task td9 = Task.Factory.StartNew(choujiang);
                Task td10 = Task.Factory.StartNew(choujiang);//开辟十条线程
                Task.WaitAll(td1, td2, td3, td4, td5, td6, td7, td8, td9, td10);//十条线程同时执行抽奖方法,并进行抽奖。
                Thread.Sleep(1000);
                string sql = "select JpLeave,jpCount from JP_test";//读取抽奖后奖品数量
                System.Data.DataTable dt = new System.Data.DataTable();
                dt = imp.GetSqlDataSet(System.Data.CommandType.Text, sql).Tables[0];
                for (int i = 0; i < dt.Rows.Count; i++)
                {
                    Console.WriteLine(dt.Rows[i]["JpLeave"] + "等奖剩余数量为:" + dt.Rows[i]["jpCount"]);//输出各个奖项的数量
                }
                Console.ReadKey();
            }
    
            /// <summary>
            /// 模拟抽奖程序
            /// </summary>
            /// <returns></returns>
            public static void choujiang()
            {
                for (int i = 0; i < 1000; i++)
                {
                    Random ran = new Random();
                    int NX = ran.Next(1, 101);//随机数,不作解释。不懂得小伙伴可自行百度。
                    StringBuilder sb = new StringBuilder();
                    sb.Append("update JP_test set jpCount=jpCount-1 where 1=1 ");
                    if (NX < 11)//抽中一等奖
                    {
                        sb.Append(" and JpLeave=1");
                        string sql = "select jpCount from JP_test where JpLeave=1";
                        int count = Convert.ToInt32(imp.GetSqlOne(System.Data.CommandType.Text, sql));//获取当前一等奖数量
                        if (count > 0)//如果数据库中一等奖数量大于0,则数量减少1
                        {
                            imp.GetSqlCount(System.Data.CommandType.Text, sb.ToString());//执行数据库
                        }
                    }
                    else if (10 < NX && NX < 21)//抽中二等奖
                    {
                        sb.Append(" and JpLeave=2");
                        string sql = "select jpCount from JP_test where JpLeave=2";
                        int count = Convert.ToInt32(imp.GetSqlOne(System.Data.CommandType.Text, sql));//获取当前二等奖数量
                        if (count > 0)//如果数据库中2等奖数量大于0,则数量减少1
                        {
                            imp.GetSqlCount(System.Data.CommandType.Text, sb.ToString());//执行数据库
                        }
                    }
                    else//抽中三等奖
                    {
                        sb.Append(" and JpLeave=3");
                        string sql = "select jpCount from JP_test where JpLeave=3";
                        int count = Convert.ToInt32(imp.GetSqlOne(System.Data.CommandType.Text, sql));//获取当前三等奖数量
                        if (count > 0)//如果数据库中3等奖数量大于0,则数量减少1
                        {
                            imp.GetSqlCount(System.Data.CommandType.Text, sb.ToString());//执行数据库
                        }
                    }
                }
            }
        }

    T_SQL代码如下:

     public class T_SQL
        {
            public static string connstr = "Data Source=SZ11120020;Initial Catalog=WZ_shop;Integrated Security=True;";
         
            SqlConnection conn = null;
            SqlCommand cmd = null;
            SqlDataAdapter adapter = null;
            SqlDataReader reader = null;
            DataSet ds = null;
             //本段代码的缺点是:没执行一个SQL语句,都会创建一次连接对象。因此效率低下。
            /// <summary>
            /// 用提供的函数,执行SQL命令,返回一个从指定连接的数据库记录集
            /// </summary>
            /// <remarks>
            /// 例如:
            /// SqlDataReader r = ExecuteReader(connString, CommandType.StoredProcedure, "PublishOrders", new SqlParameter("@prodid", 24));
            /// </remarks>
            /// <param name="connectionString">SqlConnection有效的SQL连接字符串</param>
            /// <param name="commandType">CommandType:CommandType.Text、CommandType.StoredProcedure</param>
            /// <param name="commandText">SQL语句或存储过程</param>
            /// <param name="commandParameters">SqlParameter[]参数数组</param>
            /// <returns>SqlDataReader:执行结果的记录集</returns>
            public SqlDataReader GetSqlReader(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
            {
                SqlCommand cmd = new SqlCommand();
                SqlConnection conn = new SqlConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    SqlDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                    cmd.Parameters.Clear();
                    return rdr;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
            }
    
    
            /// <summary>
            /// 为执行命令做好准备:打开数据库连接,命令语句,设置命令类型(SQL语句或存储过程),函数语取。
            /// </summary>
            /// <param name="cmd">SqlCommand 组件</param>
            /// <param name="conn">SqlConnection 组件</param>
            /// <param name="trans">SqlTransaction 组件,可以为null</param>
            /// <param name="cmdType">语句类型:CommandType.Text、CommandType.StoredProcedure</param>
            /// <param name="cmdText">SQL语句,可以为存储过程</param>
            /// <param name="cmdParms">SQL参数数组</param>
            private void PrepareCommand(SqlCommand cmd, SqlConnection conn, SqlTransaction trans, CommandType cmdType, string cmdText, SqlParameter[] cmdParms)
            {
    
                if (conn.State != ConnectionState.Open)
                    conn.Open();
    
                cmd.Connection = conn;
                cmd.CommandText = cmdText;
    
                if (trans != null)
                    cmd.Transaction = trans;
    
                cmd.CommandType = cmdType;
    
                if (cmdParms != null)
                {
                    foreach (SqlParameter parm in cmdParms)
                        cmd.Parameters.Add(parm);
                }
            }
    
            private void PrepareCommand(OleDbCommand cmd, OleDbConnection conn, OleDbTransaction trans, CommandType cmdType, string cmdText, OleDbParameter[] cmdParms)
            {
    
                if (conn.State != ConnectionState.Open)
                    conn.Open();
    
                cmd.Connection = conn;
                cmd.CommandText = cmdText;
    
                if (trans != null)
                    cmd.Transaction = trans;
    
                cmd.CommandType = cmdType;
    
                if (cmdParms != null)
                {
                    foreach (OleDbParameter parm in cmdParms)
                        cmd.Parameters.Add(parm);
                }
            }
    
            public OleDbDataReader GetOleReader(CommandType cmdType, string cmdText, params OleDbParameter[] cmdParms)
            {
                OleDbCommand cmd = new OleDbCommand();
                OleDbConnection conn = new OleDbConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    OleDbDataReader rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
                    cmd.Parameters.Clear();
                    return rdr;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
            }
    
            /// <summary>
            /// 用提供的函数,执行SQL命令,返回一个从指定连接的数据库记录集
            /// </summary>
            /// <remarks>
            /// 例如:
            /// int count = cmd.ExecuteNonQuery();
            /// </remarks>
            /// <param name="connectionString">SqlConnection有效的SQL连接字符串</param>
            /// <param name="commandType">CommandType:CommandType.Text、CommandType.StoredProcedure</param>
            /// <param name="commandText">SQL语句或存储过程</param>
            /// <param name="commandParameters">SqlParameter[]参数数组</param>
            /// <returns>SqlDataReader:执行结果的记录集</returns>
            public int GetSqlCount(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
            {
                SqlCommand cmd = new SqlCommand();
                SqlConnection conn = new SqlConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    int count = cmd.ExecuteNonQuery();
                    cmd.Parameters.Clear();
                    return count;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
                finally
                {
                    conn.Close();
                }
            }
    
            public int GetOleCount(CommandType cmdType, string cmdText, params OleDbParameter[] cmdParms)
            {
                OleDbCommand cmd = new OleDbCommand();
                OleDbConnection conn = new OleDbConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    int count = cmd.ExecuteNonQuery();
                    cmd.Parameters.Clear();
                    return count;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
                finally
                {
                    conn.Close();
                }
            }
            public DataSet GetOleDataSet(CommandType cmdType, string cmdText, params OleDbParameter[] cmdParms)
            {
                OleDbCommand cmd = new OleDbCommand();
                OleDbConnection conn = new OleDbConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    OleDbDataAdapter da = new OleDbDataAdapter();
                    da.SelectCommand = cmd;
                    DataSet ds = new DataSet();
                    da.Fill(ds, "tablename");
                    return ds;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
                finally
                {
                    conn.Close();
                }
            }
    
            public DataSet GetSqlDataSet(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
            {
                SqlCommand cmd = new SqlCommand();
                SqlConnection conn = new SqlConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    SqlDataAdapter da = new SqlDataAdapter();
                    da.SelectCommand = cmd;
                    DataSet ds = new DataSet();
                    da.Fill(ds, "tablename");
                    return ds;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
                finally
                {
                    conn.Close();
                }
            }
    
            public object GetOleOne(CommandType cmdType, string cmdText, params OleDbParameter[] cmdParms)
            {
                OleDbCommand cmd = new OleDbCommand();
                OleDbConnection conn = new OleDbConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    object one = cmd.ExecuteScalar();
                    return one;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
                finally
                {
                    conn.Close();
                }
            }
    
            public object GetSqlOne(CommandType cmdType, string cmdText, params SqlParameter[] cmdParms)
            {
                SqlCommand cmd = new SqlCommand();
                SqlConnection conn = new SqlConnection(connstr);
    
                // 我们在这里用 try/catch 是因为如果这个方法抛出异常,我们目的是关闭数据库连接,再抛出异常,
                // 因为这时不会有DataReader存在,此后commandBehaviour.CloseConnection将不会工作。
                try
                {
                    PrepareCommand(cmd, conn, null, cmdType, cmdText, cmdParms);
                    object one = cmd.ExecuteScalar();
                    return one;
                }
                catch
                {
                    conn.Close();
                    throw;
                }
                finally
                {
                    conn.Close();
                }
            }
    }
    

    下面我们看看程序执行的结果(因为近万名群众同时抽奖,同时与数据库进行交互,所以程序运行会占用15秒左右的时间,请大家耐心等待):

    呵呵:最后一等奖,二等奖的数量都变成了负数(当然三等奖也可能为负数)!如果这个活动是公司年会抽奖,那么你注定不好过年了!哈哈哈!

    究其原因,就是咱们在操作数据库的时候,没有进行并发处理。有兴趣的小虎伴们也可以尝试: Thread td1 = new Thread(choujiang);进行操作!在此就不作演示了!

    要想不被老板骂,建议:看我的上篇博客,C#并发处理 锁OR线程安全。网址:http://www.cnblogs.com/chenwolong/p/LoveFuTing.html及SQL乐观锁及悲观锁 http://www.cnblogs.com/chenwolong/p/Lock.html

    至此本篇博客的任务就算完成了,模拟并发咱们也做到了!程序写的比较简单丑陋,欢迎大家积极改善发言!

    @陈卧龙的博客

  • 相关阅读:
    Nokia N78拍照最佳设置!
    摩托Milestone购机鉴别篇
    sdf
    js随笔
    jssip无法识别以数字开始的域
    ASP.NET页面生命周期与优化
    在SQLServer2005中使用全文搜索
    简析正则表达式
    《WCF编程》之错误
    《WCF编程》之实例管理
  • 原文地址:https://www.cnblogs.com/chenwolong/p/moniBF.html
Copyright © 2020-2023  润新知