这个示例主要演示的是在多进程操作数据库时,如何避免并发重复数据入库的例子。
过多的线程理论不再阐述,网上、书上皆有。
项目采用 Asp.Net Framework 4.5 / Mysql 5.4 数据库。
首先创建一个 LockInsertDB.cs 文件,这个文件用来实现数据库添加;
1 using System;
2 using MySql;
3 using MySql.Data.MySqlClient;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Text;
7 using System.Threading.Tasks;
8
9 namespace DBImportTool
10 {
11 public class LockInsertDB : Common
12 {
13 private object obj = new object();
14 /// <summary>
15 /// 添加测试数据
16 /// </summary>
17 /// <param name="path">字段内容</param>
18 /// <param name="tname">线程名称</param>
19 public void InsertTestDB(string path, object tname)
20 {
21 MySql.Data.MySqlClient.MySqlConnection conn = new MySql.Data.MySqlClient.MySqlConnection("Server=10.17.1.57; Database=videodb; Uid=root; Pwd=xingzhi");
22 try
23 {
24 conn.Open();//打开连接
25 MySql.Data.MySqlClient.MySqlCommand cmd = new MySql.Data.MySqlClient.MySqlCommand();
26 cmd.Connection = conn;
27 //加入多线程,排它锁机制.
28 lock (obj)
29 {
30 string select = "SELECT * FROM `tbl_test` WHERE `filepath` = ‘" + path + "‘";
31 cmd.CommandText = select;
32 object val = cmd.ExecuteScalar();
33 if (val == null)
34 {
35 string sql = "INSERT INTO `tbl_test` (`filepath`, `addtime`) VALUES (‘" + path + "‘, NOW());";
36 cmd.CommandText = sql;
37 cmd.ExecuteNonQuery();
38 P(DateTime.Now.ToString() + "\t" + tname + "\tOK\t" + path + "\n");
39 }
40 else
41 {
42 P(DateTime.Now.ToString() + "\t" + tname + "\t已存在\t" + path + "\n");
43 }
44 }
45 }
46 catch
47 {
48
49 }
50 finally
51 {
52 //关闭连接
53 conn.Close();
54 }
55 }
56 /// <summary>
57 /// 测试输出
58 /// </summary>
59 /// <param name="str">输出内容字符串</param>
60 public void P(string str)
61 {
62 Console.Write(str);
63 }
64 }
65 }
再创建一个控制台工程,Main方法代码如下:
1 using System;
2 using System.Threading;
3 using System.IO;
4 using System.Collections.Generic;
5 using System.Linq;
6 using System.Text;
7 using System.Threading.Tasks;
8 namespace DBImportTool
9 {
10 public class Mcqueen
11 {
12 static void Main(string[] args)
13 {
14 Run();
15 Console.ReadLine();
16 }
17 /// <summary>
18 /// 进程数量.
19 /// </summary>
20 static int tnum = 2;
21 /// <summary>
22 /// 所对象
23 /// </summary>
24 static object obj = new object();
25 /// <summary>
26 /// 线程数组.
27 /// </summary>
28 static Thread[] threadarr = new Thread[tnum];
29 /// <summary>
30 /// 数据库插入对象.
31 /// </summary>
32 static LockInsertDB mdb = new LockInsertDB();
33 /// <summary>
34 /// 运行函数
35 /// </summary>
36 public static void Run()
37 {
38 for (int i = 0; i < tnum; i++)
39 {
40 Thread t = new Thread(T1);
41 threadarr[i] = t;
42 threadarr[i].Name = "Thread " + i.ToString() + " :";
43 }
44 int y = 0;
45 while (y < tnum)
46 {
47 threadarr[y].Start(threadarr[y].Name);
48 y++;
49 }
50 }
51 /// <summary>
52 /// 读取磁盘文件路径.
53 /// </summary>
54 /// <param name="tname">线程名称</param>
55 public static void T1(object tname)
56 {
57 DirectoryInfo di = Directory.CreateDirectory(@"F:\邢智的文件\");
58 GetFiles(di, tname);
59 }
60 /// <summary>
61 /// 采用对列入栈模式对列遍历文件夹结构.
62 /// </summary>
63 /// <param name="di">目录对象</param>
64 /// <param name="tobj">线程名称</param>
65 private static void GetFiles(DirectoryInfo di,object tobj)
66 {
67 int sgin = 1;
68 Stack<DirectoryInfo> stack_dir = new Stack<DirectoryInfo>();
69 DirectoryInfo dir = di;
70 stack_dir.Push(di);
71 while (stack_dir.Count != 0)
72 {
73 dir = stack_dir.Pop();
74 DirectoryInfo[] diarr = dir.GetDirectories();
75 foreach (DirectoryInfo d in diarr)
76 stack_dir.Push(d);
77 FileInfo[] files = dir.GetFiles();
78 foreach (FileInfo f in files)
79 {
80 mdb.InsertTestDB(f.FullName.Replace("\\", "\\\\"), tobj);
81 Thread.Sleep(200);
82 sgin++;
83 }
84 }
85 }
86 public static void P(string str)
87 {
88 Console.Write(str);
89 }
90 }
91 }
static LockInsertDB mdb = new LockInsertDB();
一开始把这句初始化对象放到了private static void GetFiles(DirectoryInfo di,object tobj) 函数里面,虽然在后续的执行过程中没有任何问题,
但是初始化运行线程时导致了进程并发进入lock锁中,后来研究一下原因,是因为每个进程都在实例化化它的时候都会在lock中分配一个标识符,
这样一来,假如A进程内实例化了A1,那么当B进程进来之后,B却不认识A线程内的A1,因此lock也就对A1不起作用了。
因此把这个类跳出线程而放到一个全局中去实例化,也就不存在这个lock失效的问题了。
运行结果如下:
如果需要模拟更多的进程用户,只需要把 tnum = 2 的数值改一下即可。