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

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

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

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

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

场下坐有近万名群众,他们在同一时刻同时抽奖,奖品分为一等奖:奔驰汽车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();
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[];
for (int i = ; 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 = ; i < ; i++)
{
Random ran = new Random();
int NX = ran.Next(, );//随机数,不作解释。不懂得小伙伴可自行百度。
StringBuilder sb = new StringBuilder();
sb.Append("update JP_test set jpCount=jpCount-1 where 1=1 ");
if (NX < )//抽中一等奖
{
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,则数量减少1
{
imp.GetSqlCount(System.Data.CommandType.Text, sb.ToString());//执行数据库
}
}
else if ( < NX && NX < )//抽中二等奖
{
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 > )//如果数据库中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 > )//如果数据库中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

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

@陈卧龙的博客

C# 模拟并发的更多相关文章

  1. 代码轮子之很简单但是挺管用的基于C# Task的模拟并发的代码

    代码轮子之很简单但是挺管用的基于C# Task的模拟并发的代码

  2. Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就绪,挂起,运行) ,***协程概念,yield模拟并发(有缺陷),Greenlet模块(手动切换),Gevent(协程并发)

    Python进阶----异步同步,阻塞非阻塞,线程池(进程池)的异步+回调机制实行并发, 线程队列(Queue, LifoQueue,PriorityQueue), 事件Event,线程的三个状态(就 ...

  3. c# 模拟并发请求 ,只能并发2个连接。

    使用 HttpWebRequest 模拟并发请求的时候,发现不管怎么提高thread 的数量,都没用,服务器端用计数器看到的都是2个连接,见下图(关于计数器怎么开,百度) 然后搜了一下,发现需要在ap ...

  4. 用压测模拟并发、并发处理(synchronized,redis分布式锁)

    使用工具:Apache an 测压命令: ab -n 100 -c 100 http://www.baidu.com -n代表模拟100个请求,-c代表模拟100个并发,相当于100个人同时访问 ab ...

  5. php模拟并发

    原文: http://blog.csdn.net/zhang_xinglong/article/details/16339867 ----------------------------------- ...

  6. 对tomcat7模拟并发请求及相关配置参数的含义

    这里的并不是真正的并发请求,因为for循环是间隔10毫秒,并且线程初始化也需要时间的,到真正执行http请求的时刻是不确定的.  tomcat 的运行状态可以在webapps下的manage项目查看, ...

  7. 自定义ThreadPoolExecutor带Queue缓冲队列的线程池 + JMeter模拟并发下单请求

    .原文:https://blog.csdn.net/u011677147/article/details/80271174 拓展: https://github.com/jwpttcg66/GameT ...

  8. 在使用HttpClient做客户端调用一个API时 模拟并发调用时发生“死锁"?

    平时还是比较喜欢看书的..但有时候遇到问题还是经常感到脑袋一蒙..智商果然是硬伤.. 同事发现了个问题,代码如下: class Program { static void Main(string[] ...

  9. Java模拟并发

    =========================one============================= public class Bingfa { public static void m ...

随机推荐

  1. PHP 下载导出中文名的文件的编码注意事项

    我的页面全部都为utf-8 在代码中我的文件名是中文名. 在创建文件时,就要将utf-8转码成gbk(用以支持中文) $file = iconv('utf-8',"gbk",$fi ...

  2. 嵌入式Linux的FTP服务端软件(stupid-ftpd)

    我自己试没成功 http://blog.csdn.net/gzshun/article/details/7358651

  3. Bootstrap插件1--tooltip

    在引入bootstrap.js之前我们需要引入jquery的js文件 既然是bootstrap的插件,那么自然需要引用bootstrap.js和bootstrap.css这2个核心文件了 这里了主要介 ...

  4. Windows下mysql自动备份的最佳方案

    网上有很多关于window下Mysql自动备份的方法,其实不乏一些不好的地方和问题,现总结出一个最好的方法供大家参考: 新建一个记事本,然后重命名为: mysql_backup.bat 然后单击右键选 ...

  5. Python的安装

    篇幅主要是别人的分享,我这里主要是添加注意点.我当初就是按照下面的图片开始安装python,安装的是python3.5,pyDev也是使用的博主的(还花了1资源分下载).但是运行程序时,一直显示 Er ...

  6. nrf51822裸机教程-GPIO

    首先看看一下相关的寄存器说明 Out寄存器 输出设置寄存器 每个比特按顺序对应每个引脚,bit0对应的就是 引脚0 该寄存器用来设置 引脚作为输出的时候的 输出电平为高还是低. 与输出设置相关的 还有 ...

  7. 【VC6】【集成工具】将输入信息集成到VC工具中

    1.首先写一个工具,可以接受外部参数, 并且输入格式必须是固定的“"%s(%d):\n", __FILE__, __LINE__”形式. 2.编译生成EXE准备进行使用: 3.在V ...

  8. 比较setImmediate(func),setTimeout(func),process.nextTick(func)

    node中的事件优先级机制: console.log('第一笔!'); process.nextTick(function() { console.log('吃个饭吧!'); setImmediata ...

  9. CSS之display:block与display:inline-block

    1.<span style="display:block; border:red solid 1px; width:100px"></span> 行级元素是 ...

  10. Cocos2d-JS引入资源

    以图片为例: 创建项目后,把图片放入res文件夹,修改 app.js var HelloWorldLayer = cc.Layer.extend({ sprite:null, ctor:functio ...