本章概要
- 什么是线程?
- 线程与进程的区别?
- .net如何开启多线程?
- Tread多线程
- 委托异步调用
- Timer类的使用
- ThreadPool线程池
一.什么是线程?
线程,顾名思义,就是一条流水线工作的过程(流水线的工作需要电源,电源就相当于cpu),而一条流水线必须属于一个车间,一个车间的工作过程是一个进程,车间负责把资源整合到一起,是一个资源单位,而一个车间内至少有一条流水线。
所以,进程只是用来把资源集中到一起(进程只是一个资源单位,或者说资源集合),而线程才是cpu上的执行单位。
多线程(即多个控制线程)的概念是,在一个进程中存在多个线程,多个线程共享该进程的地址空间,相当于一个车间内有多条流水线,都共用一个车间的资源。。例如,北京地铁与上海地铁是不同的进程,而北京地铁里的13号线是一个线程,北京地铁所有的线路共享北京地铁所有的资源,比如所有的乘客可以被所有线路拉。
二.线程与进程的区别?
-
同一个进程内的多个线程共享该进程内的地址资源
-
创建线程的开销要远小于创建进程的开销(创建一个进程,就是创建一个车间,涉及到申请空间,而且在该空间内建至少一条流水线,但创建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小)
三.net如何开启多线程?
新起一个线程的方法,可以使用Thread,BackgroundWorker ,ThreadPool,控件.BeginInvoke,委托.BeginInvoke,Timer。
创建多线程处理应用程序的最可靠方法是使用 BackgroundWorker 组件。但是,当你需要对线程进行精细控制的时候,就需要Thread。总体来说,各种方法各有各的优点,在这里不做细说。
备注:严格意义上,异步不是新线程。
四.Tread多线程
下面例子实现开启多线程,让我们来看看效果。
class threadDemo
{
private static Random random = new Random();
static void Main(string[] args)
{
Thread thread = new Thread(new ThreadStart(DemoThread)); // 无参无返回值线程
Thread thread_params = new Thread(new ParameterizedThreadStart(Demo2Thread)); // 有参无返回值线程
thread.Start(); // 启动线程
thread_params.Start("小夏天"); // 启动有参线程
Console.WriteLine("main..."); // 主线程方法
thread.Join(); // 阻塞等待线程1
thread_params.Join(); // 阻塞等待线程2
}
private static void DemoThread()
{
for (int i = 1; i <= 10; i++)
{
int t = random.Next(100);
Thread.Sleep(t); // 模拟IO阻塞
Console.WriteLine("demo:" + i.ToString());
}
}
private static void Demo2Thread(object str)
{
for (int i = 1; i <= 10; i++)
{
int t = random.Next(100);
Thread.Sleep(t); // 模拟IO阻塞
Console.WriteLine("demo_para:"+i.ToString());
}
Console.WriteLine(str);
}
}
运行结果
可以看到,线程1、2并没有影响到主线程的执行,而且线程1、线程2 是交替执行的。当一个线程阻塞的时候,另一个线程可以继续执行,这样就实现了并发的效果。
其他常用Thread方法
thread.Priority = ThreadPriority.AboveNormal; # 设置线程优先级
thread.IsBackground = true; # 设置线程是否为后台线程,或者叫守护线程。当主进程down掉以后,守护线程不管执行完否,都会跟主线程一期dowm掉。
注:ParameterizedThreadStart只能带一个参数,如需要传多个参数,可以尝试写一个类封装多个属性进行传值。另一种方式是使用lambda表达式。代码如下:
lambda表达式传多参数
static void Main(string[] args)
{
new System.Threading.Thread(() => ShowMessage("我爱你,", "小夏天!", "啊啊啊!")).Start();
Console.Read();
}
private static void ShowMessage(string msg1, string msg2, string msg3)
{
Console.WriteLine(msg1 + msg2 + msg3);
}
匿名函数也可以使用ThreadStart和ParameterizedThreadStart方法。
static void Main(string[] args)
{
new System.Threading.Thread(delegate(object msg1) {
Console.WriteLine(msg1.ToString());
}).Start();
}
但是,通过ThreadStart、ParameterizedThreadStart方式开启的多线程,返回值都是void。能完美解决参数和返回值问题的是使用异步调用的方式,异步调用和Thread相比,一个最大的劣势是不能控制优先级。下面是委托异步的方法。
委托异步调用
-
委托异步的简单调用
BeginInvoke(...)方法开启异步方法,返回一个IAsyncResult类型的ret 监听异步方法是否执行完。再调用EndInvoke(ret)方法结束异步方法,返回异步方法的返回值。
private static int Calculate(int a, int b) { System.Threading.Thread.Sleep(1000 * 10);//假如计算需要3秒钟 int c = a + b; Console.WriteLine(" 计算完成,结果:{0}+{1}={2}", a, b, c); return c; } static void Main(string[] args) { Console.WriteLine("-----------程序运行开始----------"); Func<int, int, int> action = Calculate;//声明一个委托 IAsyncResult ret = action.BeginInvoke(1, 2, null, null); Console.WriteLine("1.我不参与计算,先走了啊!"); int amount = action.EndInvoke(ret); Console.WriteLine("-----------程序运行结束----------"); Console.ReadKey(); }
-
轮询方式
上诉代码当走到EndInvoke的时候,主程序会发生阻塞,我们可以使用轮询的方式在阻塞的过程中继续执行我们的主程序代码。
private static int Calculate(int a, int b) { System.Threading.Thread.Sleep(1000 * 10);//假如计算需要3秒钟 int c = a + b; Console.WriteLine(" 计算完成,结果:{0}+{1}={2}", a, b, c); return c; } static void Main(string[] args) { Console.WriteLine("-----------程序运行开始----------"); Func<int, int, int> action = Calculate;//声明一个委托 IAsyncResult ret = action.BeginInvoke(1, 2, null, null); Console.WriteLine("我不参与计算,先走了啊!"); Console.WriteLine("正在努力计算:"); while (ret.IsCompleted == false) { Console.Write("."); System.Threading.Thread.Sleep(1000); } int amount = action.EndInvoke(ret); Console.WriteLine("-----------程序运行结束----------"); Console.ReadKey(); }
-
异步回调
以上代码还是没有体现出异步编程的效果,因为整个过程都是在“程序运行开始”和“程序运行结束”之间的,所以还有另一种实现方式,代码如下:
private delegate string FuncHandle(int data1, int data2, int data3); private static FuncHandle fh; static void Main(string[] args) { fh = Foo; AsyncCallback callback = new AsyncCallback(AsyncCallbackImpl); fh.BeginInvoke(5, 2, 0, callback, fh); Console.WriteLine("main..."); Console.Read(); } private static void AsyncCallbackImpl(IAsyncResult ar) { FuncHandle dl = ar.AsyncState as FuncHandle; string re = dl.EndInvoke(ar); Console.WriteLine(ar.AsyncState); Console.WriteLine(re); System.IO.FileInfo fi = new System.IO.FileInfo("../../demo.txt"); var cont = default(int); using (System.IO.FileStream stream = fi.OpenRead()) { byte[] buffer = new byte[1024 * 1024 * 2]; int r = stream.Read(buffer, 0, buffer.Length); var content = System.Text.Encoding.UTF8.GetString(buffer, 0, r); cont = Convert.ToInt16(content); } using (System.IO.StreamWriter sw = fi.CreateText()) { sw.WriteAsync((cont + 1).ToString()); } } private static string Foo(int data1, int data2, int data3) { return " 小夏天, " + data1.ToString() + data2.ToString() + data3.ToString(); }
Timer类的使用
Timer定时器,可以按照给定的时间间隔 定时执行一个方法。
static void Main(string[] args)
{
System.Timers.Timer t = new System.Timers.Timer(1000); //实例化Timer类,设置间隔时间为10000毫秒;
t.Elapsed += new System.Timers.ElapsedEventHandler(theout); //到达时间的时候执行事件;
t.AutoReset = true; //设置是执行一次(false)还是一直执行(true);
t.Enabled = true; //是否执行System.Timers.Timer.Elapsed事件
Console.WriteLine("main...");
Console.Read();
}
public static void theout(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine(1);
}
ThreadPool线程池
直接使用thread开启的线程,无法进行管理,如果网络负载较大,开启的多线程过多可能造成系统崩溃的情况。如果使用线程池,就方便我们控制程序。
而且线程池的开启需要耗费一定的内存空间和资源,并且很多线程处于休眠状态,或者等待资源释放。
什么是线程池?
线程池中的线程执行完指定的方法后并不会自动消除,而是以挂起状态返回线程池,如果应用程序再次向线程池发出请求,那么处以挂起状态的线程就会被激活并执行任务,而不会创建新线程,这就节约了很多开销。只有当线程数达到最大线程数量,系统才会自动销毁线程。因此,使用线程池可以避免大量的创建和销毁的开支,具有更好的性能和稳定性,其次,开发人员把线程交给系统管理,可以集中精力处理其他任务。
怎么使用线程池?
-
设置线程池最大最小
// 设置最大线程数 // 参数: // workerThreads: // 要由线程池根据需要创建的新的最小工作程序线程数。 // // completionPortThreads: // 要由线程池根据需要创建的新的最小空闲异步 I/O 线程数。 System.Threading.ThreadPool.SetMaxThreads(100, 100);
// 设置最小线程数 // 参数: // workerThreads: // 要由线程池根据需要创建的新的最小工作程序线程数。 // // completionPortThreads: // 要由线程池根据需要创建的新的最小空闲异步 I/O 线程数。 public static bool SetMinThreads(int workerThreads, int completionPortThreads);
-
将任务添加进线程池
ThreadPool.QueueUserWorkItem(new WaitCallback(方法名)); 或 ThreadPool.QueueUserWorkItem(new WaitCallback(方法名), 参数); // 只能传一个参数
const int cycleNum = 10; static void Main(string[] args) { ThreadPool.SetMinThreads(1,1); ThreadPool.SetMaxThreads(5, 5); for(int i = 1; i <= cycleNum; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(testFun),i.ToString()); } Console.WriteLine("主线程执行!"); Console.WriteLine("主线程结束!"); Console.ReadKey(); } public static void testFun(object obj) { Console.WriteLine(string.Format("{0}:第{1}个线程",DateTime.Now.ToString(),obj.ToString())); Thread.Sleep(5000); }