5天不再惧怕多线程——第一天 尝试Thread

转自http://www.cnblogs.com/huangxincheng/archive/2012/03/14/2395279.html

原本准备在mongodb之后写一个lucene.net系列,不过这几天用到多线程时才发现自己对多线程的了解少之又少,仅仅停留在lock上面,

故这几天看了下线程参考手册结合自己的心得整理一下放在博客上作为自己的学习笔记。

     好了,我们知道“负载”是一个很时尚,很牛X的玩意,往大处说,网站需要负载,数据库需要负载。往小处说,线程也需要负载,面对海量的

用户请求,我们的单线程肯定扛不住,那么怎么办,一定要负载,所以说多线程是我们码农必须要熟练掌握的一门技术。

    在framework中给我们提供了一个Threading命名空间,下面是一个msdn上不完整的截图:

在后面的系列中我也是主要整理这几个类的使用方法和应用场景。

一:Thread的使用

      我们知道这个类代表处理器线程,在Thread中有几个比较常用和重要的方法。

<1> sleep:  这个算是最简单的了。

<2> join:    这个可以让并发行处理变成串行化,什么意思呢?上代码说话最清楚。

复制代码
 1 class Test
2 {
3 static void Main()
4 {
5 Thread t = new Thread(Run);
6
7 t.Start();
8
9 //Join相当于把Run方法内嵌如此
10 t.Join();
11
12 //该死的t.Join(),害的我主线程必须在你执行完后才能执行。
13 Console.WriteLine("我是主线程:" + Thread.CurrentThread.GetHashCode());
14 }
15
16 static void Run()
17 {
18 //等待5s
19 Thread.Sleep(5000);
20
21 Console.WriteLine("我是线程:" + Thread.CurrentThread.GetHashCode());
22 }
23 }
复制代码

<3> Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。

        ① Interrupt:  抛出的是 ThreadInterruptedException 异常。

                 Abort:  抛出的是  ThreadAbortException 异常。

        ② Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个

                             contine操作。

                 Abort:这个就是相当于一个break操作,工作线程彻底死掉。 

 

Interrupt:

复制代码
 1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 Thread t = new Thread(new ThreadStart(Run));
8
9 t.Start();
10
11 //阻止动作
12 t.Interrupt();
13
14 Console.Read();
15 }
16
17 static void Run()
18 {
19 for (int i = 1; i <= 3; i++)
20 {
21 Stopwatch watch = new Stopwatch();
22
23 try
24 {
25 watch.Start();
26 Thread.Sleep(2000);
27 watch.Stop();
28
29 Console.WriteLine("第{0}延迟执行:{1}ms", i, watch.ElapsedMilliseconds);
30 }
31 catch (ThreadInterruptedException e)
32 {
33 Console.WriteLine("第{0}延迟执行:{1}ms,不过抛出异常", i, watch.ElapsedMilliseconds);
34 }
35 }
36 }
37 }
38 }
复制代码

 

Abort:   工作线程直接退出,不带走一片云彩。

复制代码
 1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 Thread t = new Thread(new ThreadStart(Run));
8
9 t.Start();
10
11 Thread.Sleep(100);
12
13 //阻止动作
14 t.Abort();
15
16 Console.Read();
17 }
18
19 static void Run()
20 {
21 for (int i = 1; i <= 3; i++)
22 {
23 Stopwatch watch = new Stopwatch();
24
25 try
26 {
27 watch.Start();
28 Thread.Sleep(2000);
29 watch.Stop();
30
31 Console.WriteLine("第{0}延迟执行:{1}ms", i, watch.ElapsedMilliseconds);
32 }
33 catch (ThreadAbortException e)
34 {
35 Console.WriteLine("第{0}延迟执行:{1}ms,不过抛出异常", i, watch.ElapsedMilliseconds);
36 }
37 }
38 }
39 }
40 }
复制代码

 


二:线程使用场景

    可能线程的使用有点类似wcf,做一些耗时但不很及时的需求,比如可以开线程下图片,连接数据库等等,当然线程可以用来做负载,这里就做

一个小demo,找一个美女网站,面对如此多的图片,一个线程真的吃不消啊,

看了下网站主体上有4个tab页,那么我们就开4个线程来负载

复制代码
  1  class Program
2 {
3 static void Main(string[] args)
4 {
5 string[] str = { "model", "sexy", "belle", "stars" };
6
7 for (int url = 0; url < str.Length; url++)
8 {
9 Thread thread = new Thread(DownLoad);
10
11 thread.Start(str[url]);
12 }
13 Console.Read();
14 }
15
16 public static void DownLoad(object category)
17 {
18 string url = string.Empty;
19
20 for (int purl = 9014; purl > 10; purl--)
21 {
22 for (int pageSize = 0; pageSize < 20; pageSize++)
23 {
24 try
25 {
26 if (pageSize == 0)
27 url = "http://www.mm8mm8.com/" + category + "/" + purl + ".html";
28 else
29 url = "http://www.mm8mm8.com/" + category + "/" + purl + "_" + pageSize + ".html";
30
31 //创建http链接
32 var request = (HttpWebRequest)WebRequest.Create(url);
33
34 request.Timeout = 1000 * 5; //5s过期
35
36 var response = (HttpWebResponse)request.GetResponse();
37
38 Stream stream = response.GetResponseStream();
39
40 StreamReader sr = new StreamReader(stream);
41
42 string content = sr.ReadToEnd();
43
44 var list = GetHtmlImageUrlList(content);
45
46 WebClient client = new WebClient();
47
48 string[] directory = { @"C:\MM\", @"D:\MM\", @"E:\MM\", @"F:\MM\" };
49
50 var directoryName = directory[new Random().Next(0, directory.Length)];
51
52 if (!Directory.Exists(directoryName))
53 Directory.CreateDirectory(directoryName);
54
55 var fileName = string.Empty;
56
57 if (list.Count == 0)
58 {
59 Console.WriteLine("时间:" + DateTime.Now + " 当前网址:" + url + " 未发现图片");
60 break;
61 }
62
63 try
64 {
65
66 fileName = category + "_" + purl + "_" + (pageSize + 1) + ".jpg";
67
68 var localFile = directoryName + fileName;
69
70 var imageRequest = (HttpWebRequest)WebRequest.Create(list[0]);
71
72 imageRequest.Timeout = 1000 * 5; //5s 超时
73
74 var imageResponse = (HttpWebResponse)imageRequest.GetResponse();
75
76 var s = imageResponse.GetResponseStream();
77
78 Image image = Image.FromStream(s);
79
80 image.Save(localFile);
81
82 image.Dispose();
83
84 Console.WriteLine("时间:" + DateTime.Now + " 图片:" + fileName + " 已经下载 存入磁盘位置:" + localFile);
85
86 }
87 catch (Exception e)
88 {
89 Console.WriteLine("时间:" + DateTime.Now + " 当前图片:" + fileName + " 错误信息:" + e.Message);
90 continue;
91 }
92 }
93 catch (Exception ex)
94 {
95 Console.WriteLine("时间:" + DateTime.Now + " 当前网址:" + url + " 错误信息:" + ex.Message);
96 }
97 }
98 }
99 }
100
101 /// <summary>
102 /// 取得HTML中所有图片的 URL。
103 /// </summary>
104 /// <param name="sHtmlText">HTML代码</param>
105 /// <returns>图片的URL列表</returns>
106 public static List<string> GetHtmlImageUrlList(string sHtmlText)
107 {
108 // 定义正则表达式用来匹配 img 标签
109 Regex regImg = new Regex(@"<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
110
111 // 搜索匹配的字符串
112 MatchCollection matches = regImg.Matches(sHtmlText);
113
114 List<string> sUrlList = new List<string>();
115
116 // 取得匹配项列表
117 foreach (Match match in matches)
118 sUrlList.Add(match.Groups["imgUrl"].Value);
119 return sUrlList;
120 }
121 }
复制代码


三:对线程的一些思考

    我们知道线程的优点还是比较多的,每个线程都需要默认的堆栈空间,所以说线程数受到内存空间大小的限制,如果线程数开的太多

反而适得其反,进程被分配的时间片会被线程分的更细,也就导致了处理器需要更频繁的在线程之间来回切换。

原本准备在mongodb之后写一个lucene.net系列,不过这几天用到多线程时才发现自己对多线程的了解少之又少,仅仅停留在lock上面,

故这几天看了下线程参考手册结合自己的心得整理一下放在博客上作为自己的学习笔记。

     好了,我们知道“负载”是一个很时尚,很牛X的玩意,往大处说,网站需要负载,数据库需要负载。往小处说,线程也需要负载,面对海量的

用户请求,我们的单线程肯定扛不住,那么怎么办,一定要负载,所以说多线程是我们码农必须要熟练掌握的一门技术。

    在framework中给我们提供了一个Threading命名空间,下面是一个msdn上不完整的截图:

在后面的系列中我也是主要整理这几个类的使用方法和应用场景。

一:Thread的使用

      我们知道这个类代表处理器线程,在Thread中有几个比较常用和重要的方法。

<1> sleep:  这个算是最简单的了。

<2> join:    这个可以让并发行处理变成串行化,什么意思呢?上代码说话最清楚。

复制代码
 1 class Test
2 {
3 static void Main()
4 {
5 Thread t = new Thread(Run);
6
7 t.Start();
8
9 //Join相当于把Run方法内嵌如此
10 t.Join();
11
12 //该死的t.Join(),害的我主线程必须在你执行完后才能执行。
13 Console.WriteLine("我是主线程:" + Thread.CurrentThread.GetHashCode());
14 }
15
16 static void Run()
17 {
18 //等待5s
19 Thread.Sleep(5000);
20
21 Console.WriteLine("我是线程:" + Thread.CurrentThread.GetHashCode());
22 }
23 }
复制代码

<3> Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。

        ① Interrupt:  抛出的是 ThreadInterruptedException 异常。

                 Abort:  抛出的是  ThreadAbortException 异常。

        ② Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个

                             contine操作。

                 Abort:这个就是相当于一个break操作,工作线程彻底死掉。 

 

Interrupt:

复制代码
 1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 Thread t = new Thread(new ThreadStart(Run));
8
9 t.Start();
10
11 //阻止动作
12 t.Interrupt();
13
14 Console.Read();
15 }
16
17 static void Run()
18 {
19 for (int i = 1; i <= 3; i++)
20 {
21 Stopwatch watch = new Stopwatch();
22
23 try
24 {
25 watch.Start();
26 Thread.Sleep(2000);
27 watch.Stop();
28
29 Console.WriteLine("第{0}延迟执行:{1}ms", i, watch.ElapsedMilliseconds);
30 }
31 catch (ThreadInterruptedException e)
32 {
33 Console.WriteLine("第{0}延迟执行:{1}ms,不过抛出异常", i, watch.ElapsedMilliseconds);
34 }
35 }
36 }
37 }
38 }
复制代码

 

Abort:   工作线程直接退出,不带走一片云彩。

复制代码
 1 namespace Test
2 {
3 class Program
4 {
5 static void Main(string[] args)
6 {
7 Thread t = new Thread(new ThreadStart(Run));
8
9 t.Start();
10
11 Thread.Sleep(100);
12
13 //阻止动作
14 t.Abort();
15
16 Console.Read();
17 }
18
19 static void Run()
20 {
21 for (int i = 1; i <= 3; i++)
22 {
23 Stopwatch watch = new Stopwatch();
24
25 try
26 {
27 watch.Start();
28 Thread.Sleep(2000);
29 watch.Stop();
30
31 Console.WriteLine("第{0}延迟执行:{1}ms", i, watch.ElapsedMilliseconds);
32 }
33 catch (ThreadAbortException e)
34 {
35 Console.WriteLine("第{0}延迟执行:{1}ms,不过抛出异常", i, watch.ElapsedMilliseconds);
36 }
37 }
38 }
39 }
40 }
复制代码

 


二:线程使用场景

    可能线程的使用有点类似wcf,做一些耗时但不很及时的需求,比如可以开线程下图片,连接数据库等等,当然线程可以用来做负载,这里就做

一个小demo,找一个美女网站,面对如此多的图片,一个线程真的吃不消啊,

看了下网站主体上有4个tab页,那么我们就开4个线程来负载

复制代码
  1  class Program
2 {
3 static void Main(string[] args)
4 {
5 string[] str = { "model", "sexy", "belle", "stars" };
6
7 for (int url = 0; url < str.Length; url++)
8 {
9 Thread thread = new Thread(DownLoad);
10
11 thread.Start(str[url]);
12 }
13 Console.Read();
14 }
15
16 public static void DownLoad(object category)
17 {
18 string url = string.Empty;
19
20 for (int purl = 9014; purl > 10; purl--)
21 {
22 for (int pageSize = 0; pageSize < 20; pageSize++)
23 {
24 try
25 {
26 if (pageSize == 0)
27 url = "http://www.mm8mm8.com/" + category + "/" + purl + ".html";
28 else
29 url = "http://www.mm8mm8.com/" + category + "/" + purl + "_" + pageSize + ".html";
30
31 //创建http链接
32 var request = (HttpWebRequest)WebRequest.Create(url);
33
34 request.Timeout = 1000 * 5; //5s过期
35
36 var response = (HttpWebResponse)request.GetResponse();
37
38 Stream stream = response.GetResponseStream();
39
40 StreamReader sr = new StreamReader(stream);
41
42 string content = sr.ReadToEnd();
43
44 var list = GetHtmlImageUrlList(content);
45
46 WebClient client = new WebClient();
47
48 string[] directory = { @"C:\MM\", @"D:\MM\", @"E:\MM\", @"F:\MM\" };
49
50 var directoryName = directory[new Random().Next(0, directory.Length)];
51
52 if (!Directory.Exists(directoryName))
53 Directory.CreateDirectory(directoryName);
54
55 var fileName = string.Empty;
56
57 if (list.Count == 0)
58 {
59 Console.WriteLine("时间:" + DateTime.Now + " 当前网址:" + url + " 未发现图片");
60 break;
61 }
62
63 try
64 {
65
66 fileName = category + "_" + purl + "_" + (pageSize + 1) + ".jpg";
67
68 var localFile = directoryName + fileName;
69
70 var imageRequest = (HttpWebRequest)WebRequest.Create(list[0]);
71
72 imageRequest.Timeout = 1000 * 5; //5s 超时
73
74 var imageResponse = (HttpWebResponse)imageRequest.GetResponse();
75
76 var s = imageResponse.GetResponseStream();
77
78 Image image = Image.FromStream(s);
79
80 image.Save(localFile);
81
82 image.Dispose();
83
84 Console.WriteLine("时间:" + DateTime.Now + " 图片:" + fileName + " 已经下载 存入磁盘位置:" + localFile);
85
86 }
87 catch (Exception e)
88 {
89 Console.WriteLine("时间:" + DateTime.Now + " 当前图片:" + fileName + " 错误信息:" + e.Message);
90 continue;
91 }
92 }
93 catch (Exception ex)
94 {
95 Console.WriteLine("时间:" + DateTime.Now + " 当前网址:" + url + " 错误信息:" + ex.Message);
96 }
97 }
98 }
99 }
100
101 /// <summary>
102 /// 取得HTML中所有图片的 URL。
103 /// </summary>
104 /// <param name="sHtmlText">HTML代码</param>
105 /// <returns>图片的URL列表</returns>
106 public static List<string> GetHtmlImageUrlList(string sHtmlText)
107 {
108 // 定义正则表达式用来匹配 img 标签
109 Regex regImg = new Regex(@"<img\b[^<>]*?\bsrc[\s\t\r\n]*=[\s\t\r\n]*[""']?[\s\t\r\n]*(?<imgUrl>[^\s\t\r\n""'<>]*)[^<>]*?/?[\s\t\r\n]*>", RegexOptions.IgnoreCase);
110
111 // 搜索匹配的字符串
112 MatchCollection matches = regImg.Matches(sHtmlText);
113
114 List<string> sUrlList = new List<string>();
115
116 // 取得匹配项列表
117 foreach (Match match in matches)
118 sUrlList.Add(match.Groups["imgUrl"].Value);
119 return sUrlList;
120 }
121 }
复制代码


三:对线程的一些思考

    我们知道线程的优点还是比较多的,每个线程都需要默认的堆栈空间,所以说线程数受到内存空间大小的限制,如果线程数开的太多

反而适得其反,进程被分配的时间片会被线程分的更细,也就导致了处理器需要更频繁的在线程之间来回切换。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Qt5 中的 QObject 类提供了一个名为 moveToThread() 的函数,可以将对象从当前线程移动到另一个线程。这样可以使用多线程来并行处理任务,提高程序的性能。使用 moveToThread() 函数时需要注意,被移动到的线程需要先启动,否则会产生错误。 ### 回答2: Qt5是一个开发跨平台应用程序的框架,其中的多线程技术可以使程序效率更高。Qt5中的多线程模型做了大量的工作来简化一个活动对象(也就是一个线程)在多个线程之间被转移时的代码。其中一种常见的实现方式是使用QThread类。 在Qt5中可以使用QThread类来创建一个线程,从而使主线程和子线程可以并发执行。但是,如果在子线程中访问主线程中的UI控件或者调用主线程中的函数的话,就需要使用Qt5中的MovetoThread机制。因为Qt5默认情况下,只有创建控件的主线程才可以操作该控件,如果在其他线程中修改控件,会导致程序崩溃。 MovetoThread的机制就是将主线程中的控件或者函数移动到子线程中执行,以避免跨线程操作UI控件造成崩溃的问题。具体操作步骤如下: 1. 通过QObject::moveToThread()函数将控件或对象移到新线程中; 2. 创建新线程对象; 3. 将需要在新线程中执行的操作绑定到信号槽上。 需要注意的是: 1. 使用MovetoThread机制时,控件必须继承自QObject,才能使用moveToThread()函数; 2. 通过signal-slot方式通信时,可以使用多种数据类型,如QString、QByteArray等,但是不能传递QWidget类型的对象; 3. 在线程中,应该重写run()函数来定义需要执行的操作,但避免直接修改UI控件; 4. 在线程停止之前,需要通过wait()或quit()减轻其负担。 总之,Qt5的MovetoThread机制可以更好地实现多线程操作,同时克服跨线程操作控件的问题,确保程序的稳定性。 ### 回答3: Qt5的多线程中,MoveToThread是一个非常实用的功能。它可以让一个QObject对象从一个线程移动到另一个线程,从而实现线程的切换。在之前的Qt版本中,多线程操作通常需要在各个线程自己的对象中进行,但是Qt5的扩展使得利用其他线程的对象变得更为容易。 其实Qt5关于多线程MoveToThread功能是通过类QThread的方法moveToThread子类继承QThread可以完成该操作,该类的该方法会将对象加入线程队列,等待目标线程执行。同时需要注意到的是,该方法只能被主线程调用。 使用该功能的目的通常是为了防止UI线程(主线程)被阻塞,因为UI线程负责响应用户输入事件,并显示图形化结果,一旦UI线程被长时间阻塞,那么整个应用程序将会卡住,导致非常不友好的用户体验。这是非常不可接受的,因此我们需要将耗时的操作从UI线程中移除,以允许UI线程响应其他事件。 MoveToThread的程序代码通常如下,可以将需要部署到工作线程的对象定义为堆对象(QPointer)或共享指针(std::shared_ptr),以便在工作线程执行时避免对象的销毁: ``` Worker* worker = new Worker; //需要其他线程执行的对象(必须是QObject的子类) QThread* workerThread = new QThread; //启动工作线程 worker->moveToThread(workerThread); workerThread->start(); ``` 然而,需要注意的是,如果QObject派生类高度依赖于QObject的常规信号槽机制,则应该通过(QMetaObject::invokeMethod)调用该方法,而不能直接从工作线程对该对象调用该方法,使得能够进程线程的切换。 总的来说,Qt5的多线程MoveToThread功能使得程序在实现多线程操作时更为高效、更易于维护,避免了因为长时间阻塞UI线程而导致的应用程序卡顿或死机。但是,在实现该功能时,程序员需要小心处理程序设计,避免其他线程啊阻塞工作线程并确保工作线程的任务安全。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值