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

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

@陈卧龙的博客

 

相关文章
|
4月前
|
SQL 传感器 开发框架
今天我们聊聊C#的并发和并行
今天我们聊聊C#的并发和并行
102 1
|
7月前
|
数据库 C#
[C#] 在异步请求并发情况下,dbcontext的安全问题
摘要: 在多线程异步环境中,偶发的数据库修改失败可能因并发的`dbContext`操作引起,当一个线程的修改未保存时,另一线程尝试相同操作会导致错误。另外,单次执行成功但随后失败的情况可能源于`dbContext`的瞬时生命周期。若`saveChangesAsync()`在刷新页面请求到来前未完成,新的请求可能会尝试在写操作期间读取数据,从而引发问题。
|
存储 缓存 安全
C#的并发机制优秀在哪?
笔者上次用C#写.Net代码差不多还是10多年以前,由于当时Java已经颇具王者风范,Net几乎被打得溃不成军。因此当时笔者对于这个.Net的项目态度比较敷衍了事,没有对其中一些优秀机制有很深的了解,在去年写《C和Java没那么香了,高并发时代谁能称王》时都没给.Net以一席之地,不过最近恰好机缘巧合,我又接手了一个Windows方面的项目,这也让我有机会重新审视一下自己关于.Net框架的相关知识。 项目原型要实现的功能并不复杂,主要就是记录移动存储设备中文件拷出的记录,而且需要尽可能少的占用系统资源,而在开发过程中的一个现象令我颇我惊异,在使用Invoke方法记录文件拷出情况时,程序执行效率
C#的并发机制优秀在哪?
|
安全 C#
C#并发实战Parallel.ForEach使用
C#并发实战Parallel.ForEach使用 前言:最近给客户开发一个伙食费计算系统,大概需要计算2000个人的伙食。需求是按照员工的预定报餐计划对消费记录进行检查,如有未报餐有刷卡或者有报餐没刷卡的要进行一定的金额扣减等一系列规则。
1130 0
|
安全 C#
C#并发处理-锁OR线程安全?
每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客! 当然,题外话说多了,咱进入正题! 背景 基于任务的程序设计、命令式数据并行和任务并行都要求能够支持并发更新的数组、列表和集合。
2348 0
|
安全 程序员 C#
C# 集合-并发处理-锁OR线程
每次写博客,第一句话都是这样的:程序员很苦逼,除了会写程序,还得会写博客!当然,希望将来的一天,某位老板看到此博客,给你的程序员职工加点薪资吧!因为程序员的世界除了苦逼就是沉默。我眼中的程序员大多都不爱说话,默默承受着编程的巨大压力,除了技术上的交流外,他们不愿意也不擅长和别人交流,更不乐意任何人走进他们的内心!    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来。
1239 0
|
存储 C# 数据库
C# 数据库并发的解决方案(通用版、EF版)
还是那句老话:十年河东,十年河西,莫欺骚年穷!~_~ 打错个字,应该是莫欺少年穷! 学历代表你的过去,能力代表你的现在,学习代表你的将来。 学无止境,精益求精。 自ASP.NET诞生以来,微软提供了不少控制并发的方法,在了解这些控制并发的方法前,我们先来简单介绍下并发! 并发:同一时间或者同一时刻多个访问者同时访问某一更新操作时,会产生并发! 针对并发的处理,又分为悲观并发处理和乐观并发处理 所谓悲观/乐观并发处理,可以这样理解: 悲观者认为:在程序的运行过程中,并发很容易发生滴,因此,悲观者提出了他们的处理模式:在我执行一个方法时,不允许其他访问者介入这个方法。
3226 0
|
监控 测试技术 C#
C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍
原文:C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 例子主要包括SocketAsyncEventArgs通讯封装、服务端实现日志查看、SCOKET列表、上传、下载、远程文件流、吞吐量协议,用于测试SocketAsyncEventArgs的性能和压力,最大连接数支持65535个长连接,最高命令交互速度达到250MB/S(使用的是127.0.0.1的方式,相当于千兆网卡1Gb=125MB/S两倍的吞吐量)。
3272 0
|
C# 缓存
C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装
原文:C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 1、SocketAsyncEventArgs介绍 SocketAsyncEventArgs是微软提供的高性能异步Socket实现类,主要为高性能网络服务器应用程序而设计,主要是为了避免在在异步套接字 I/O 量非常大时发生重复的对象分配和同步。
3611 0