细说C#多线程那些事-线程基础
我第一次接触“线程”的概念时,觉得它深奥难懂,看了好多本书,花了很长时间才领悟到它的真谛。现在我就以一个初学者的心态,把我所理解的“多线程”描述给大家。这一次是系列文章,比较完整的展示与线程相关的基本概念。希望对初学者有所帮助。
如果你是高手,请你别继续看,会浪费你宝贵的时间。
一、基本概念
什么是进程?
当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。
什么是线程?
线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数。
什么是多线程?
多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
前台线程后台线程?
应用程序的主线程和通过构造一个Thread对象来显式创建的任何线程都默认是前台线程。相反线程池线程默认为后台线程。另外由进入托管执行环境的本机代码创建的任何线程都被标记为后台线程。
在线程的生命周期中,任何时候都可以从前台变为后台,或者从后台变为前台。
前台线程能阻止应用程序的终结。一直到所有的前台线程终止后,CLR才能关闭应用程序(即卸载承载的应用程序域)。
后台线程(有时也叫守护线程)被CLR认为是程序执行中可做出牺牲的途径,即在任何时候(即使这个线程此时正在执行某项工作)都可能被忽略。因此,如果所有的前台线程终止,当应用程序域卸载时,所以的后台线程也会被自动终止。
线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。
二、线程的生命周期
线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。
线程生命周期中的各种状态:
未启动状态:当线程实例被创建但 Start 方法未被调用时的状况(将该线程标记为可以运行的状态,但具体执行时间由cpu决定。)。
就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
不可运行状态:下面的几种情况下线程是不可运行的:(已经调用 Sleep 方法,已经调用 Wait 方法,通过 I/O 操作阻塞)
死亡状态:当线程已完成执行或已中止时的状况。

三、线程
1、主线程
进程中第一个被执行的线程称为主线程
using System;
using System.Threading; namespace Threading
{
class Program
{
static void Main(string[] args)
{
Thread th = Thread.CurrentThread;
th.Name = "MainThread";
Console.WriteLine("This is {0}", th.Name);
Console.ReadKey();
}
}
}
输出:This is MainThread
2、线程的创建
var th = new Thread(new ThreadStart(() =>
{
//
}));
th.Start(); var ts = new ThreadStart(() =>
{
//
});
var th1 = new Thread(ts);
th1.Start();
using System;
using System.Threading; namespace Threading
{
class Program
{
public static void Thread1()
{
Console.WriteLine("Thread1 starts");
}
public static void Thread2(object data)
{
Console.WriteLine("Thread2 starts,para:{0}", data.ToString());
}
static void Main(string[] args)
{
var t1 = new Thread(Thread1);
t1.Start(); var t2 = new Thread(Thread2);
t2.Start("thread2");
Console.ReadKey();
}
}
}
输入:
Thread1 starts
Thread2 starts,para:thread2
3、线程的管理
sleep()挂起和Abort() 销毁线程
通过抛出 threadabortexception 在运行时中止线程。这个异常不能被捕获,如果有 finally 块,控制会被送至 finally 块
using System;
using System.Threading; namespace Threading
{
class Program
{
public static void Thread1()
{
Console.WriteLine("Thread1 starts");
Console.WriteLine("Thread1 Paused for 5 seconds");
Thread.Sleep();
Console.WriteLine("Thread1 resumes");
}
static void Main(string[] args)
{
var t1 = new Thread(Thread1);
t1.Start(); Console.ReadKey();
}
}
}
线程挂起代码
using System;
using System.Threading; namespace Threading
{
class Program
{
public static void Thread1()
{
try
{
Console.WriteLine("Thread1 starts");
for (int i = ; i <= ; i++)
{
Thread.Sleep();
Console.WriteLine(i);
}
Console.WriteLine("Thread1 Completed");
}
catch (ThreadAbortException ex)
{
Console.WriteLine("Thread1 Abort Exception");
}
finally
{
Console.WriteLine("Couldn't catch the Thread1 Exception");
} }
static void Main(string[] args)
{
//开启子线程
var t1 = new Thread(Thread1);
t1.Start(); //主线程挂起2s
Thread.Sleep(); //终止t1子线程
t1.Abort(); Console.ReadKey();
}
}
}
线程销毁代码
销毁代码执行结果:

四、线程池
在多线程程序中,线程把大部分的时间花费在等待状态,等待某个事件发生,然后才能给予响应我们一般用ThreadPool(线程池)来解决;线程平时都处于休眠状态,只是周期性地被唤醒我们使用使用Timer(定时器)来解决。
由于线程的创建和销毁需要耗费一定的开销,过多的使用线程会造成内存资源的浪费,出于对性能的考虑,于是引入了线程池的概念。线程池维护一个请求队列,线程池的代码从队列提取任务,然后委派给线程池的一个线程执行,线程执行完不会被立即销毁,这样既可以在后台执行任务,又可以减少线程创建和销毁所带来的开销。线程池线程默认为后台线程。
线程池自动管理线程线程的创建和销毁。
代码展示:
using System;
using System.Threading; namespace Threading
{
class Program
{
public static void Thread1(object data)
{
Console.WriteLine("Thread1 => {0}",data.ToString()); }
static void Main(string[] args)
{
//控制线程数大小
//第一个参数是:线程池中辅助线程的最大数目
//第二个参数是:线程池中异步 I/O 线程的最大数目
ThreadPool.SetMaxThreads(, );
for (int i = ; i < ; i++)
{
//ThreadPool是静态类无需实例化,
//ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
ThreadPool.QueueUserWorkItem(Thread1, i);
}
Console.WriteLine("Thread1 sleep");
Thread.Sleep(); Console.WriteLine("Thread1 end");
Console.ReadKey();
}
}
}
运行结果:

但是为什么最开始输出Thread1 sleep?有时候也会在中间随机输出呢?
其实,线程池的启动和终止不是我们程序所能控制的,线程池中的线程执行完之后是没有返回值的,我们可以用ManualResetEvent通知一个或多个正在等待的线程已发生事件
修改后的代码:
using System;
using System.Threading; namespace Threading
{
class Program
{
//新建ManualResetEvent对象并且初始化为无信号状态
private static ManualResetEvent mre = new ManualResetEvent(false);
public static void Thread1(object data)
{
Console.WriteLine("Thread1 => {0}",data.ToString());
if (Convert.ToInt32(data) == )
{
mre.Set();
}
}
static void Main(string[] args)
{
//控制线程数大小
//第一个参数是:线程池中辅助线程的最大数目
//第二个参数是:线程池中异步 I/O 线程的最大数目
ThreadPool.SetMaxThreads(, );
for (int i = ; i < ; i++)
{
//ThreadPool是静态类无需实例化,
//ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
ThreadPool.QueueUserWorkItem(Thread1, i);
} //阻止当前线程,直到当前 WaitHandle 收到信号为止。
mre.WaitOne(Timeout.Infinite, true); Console.WriteLine("Thread1 sleep");
Thread.Sleep(); Console.WriteLine("Thread1 end");
Console.ReadKey();
}
}
}
输入结果:

ok,搞定。
参考资料:
ThreadPool:https://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0
ManualResetEvent:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx
五、总结
多线程的好处:
可以提高CPU的利用率。在多线程程序中,一个线程必须等待的时候,CPU可以运行其它的线程而不是等待,这样就大大提高了程序的效率。
多线程的不利方面:
线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
多线程需要协调和管理,所以需要CPU时间跟踪线程;
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题;
线程太多会导致控制太复杂,最终可能造成很多Bug;
细说C#多线程那些事-线程基础的更多相关文章
- 细说C#多线程那些事 - 线程同步和多线程优先级
上个文章分享了一些多线程的一些基础的知识,今天我们继续学习. 一.Task类 上次我们说了线程池,线程池的QueueUserWorkItem()方法发起一次异步的线程执行很简单 但是该方法最大的问题是 ...
- JAVA与多线程开发(线程基础、继承Thread类来定义自己的线程、实现Runnable接口来解决单继承局限性、控制多线程程并发)
实现线程并发有两种方式:1)继承Thread类:2)实现Runnable接口. 线程基础 1)程序.进程.线程:并行.并发. 2)线程生命周期:创建状态(new一个线程对象).就绪状态(调用该对象的s ...
- C#多线程开发-线程基础 01
最近由于工作的需要,一直在使用C#的多线程进行开发,其中也遇到了很多问题,但也都解决了.后来发觉自己对于线程的知识和运用不是很熟悉,所以将利用几篇文章来系统性的学习汇总下C#中的多线程开发. 线程基础 ...
- Android多线程研究(1)——线程基础及源代码剖析
从今天起我们来看一下Android中的多线程的知识,Android入门easy,可是要完毕一个完好的产品却不easy,让我们从线程開始一步步深入Android内部. 一.线程基础回想 package ...
- C#当中的多线程_线程基础
前言 最近工作不是很忙,想把买了很久了的<C#多线程编程实战>看完,所以索性把每一章的重点记录一下,方便以后回忆. 第1章 线程基础 1.创建一个线程 using System; usin ...
- C#多线程编程系列(二)- 线程基础
目录 C#多线程编程系列(二)- 线程基础 1.1 简介 1.2 创建线程 1.3 暂停线程 1.4 线程等待 1.5 终止线程 1.6 检测线程状态 1.7 线程优先级 1.8 前台线程和后台线程 ...
- Android多线程研究(1)——线程基础及源码剖析
从今天起我们来看一下Android中的多线程的知识,Android入门容易,但是要完成一个完善的产品却不容易,让我们从线程开始一步步深入Android内部. 一.线程基础回顾 package com. ...
- java核心技术-多线程之线程基础
说起线程,无法免俗首先要弄清楚的三个概念就是:进程.线程.协程.OK,那什么是进程,什么是线程,哪协程又是啥东西.进程:进程可以简单的理解为运行在操作系统中的程序,程序时静态代码,进程是动态运行着的代 ...
- 【Java_多线程并发编程】基础篇—线程状态及实现多线程的两种方式
1.Java多线程的概念 同一时间段内,位于同一处理器上多个已开启但未执行完毕的线程叫做多线程.他们通过轮寻获得CPU处理时间,从而在宏观上构成一种同时在执行的假象,实质上在任意时刻只有一个线程获得C ...
随机推荐
- 解决easy ui两次请求服务器的问题
目前该问题已经在1.4.1版本中解决了 本文引用自:http://www.cnblogs.com/Reaver/p/4056770.html,原文博主:flyreaver 我在使用过程中遇到了easy ...
- ASP.NET发布网站遇到的几个问题
环境背景 操作系统:Win7 开发工具:Visual Studio 2013 Framework: Framework 4.0 Issue1: 无法识别的属性“targetFramework” 解决方 ...
- [转帖]迅为4412开发板最小linux系统的存储空间修改
本文转自迅为论坛:http://www.topeetboard.com 最小linux系统的存储空间修改以修改成 1G 存储空间为例来修改,如果需要改成其他大小的存储空间,参照此方法修改即可. 首先连 ...
- Webpack+React+ES6入门指南[转]
React无疑是今年最火的前端框架,github上的star直逼30,000,基于React的React Native的star也直逼20,000.有了React,组件化似乎不再步履蹒跚,有了Reac ...
- [转]10个学习Android开发的网站推荐
本文转自:http://blog.csdn.net/i_lovefish/article/details/43950893 1. Android Developers 作为一个Android开发者,官 ...
- 【ASP.NET 基础】表单和控件
1.HTML表单的提交方式 对于一个普通HTML表单来说,它有两个重要的属性:action 和 method.action属性指明当前表单提交之后由哪个程序来处理,这个处理程序可以是任何动态网页或者 ...
- 51nod-1661 1661 黑板上的游戏(组合游戏)
题目链接: 1661 黑板上的游戏 Alice和Bob在黑板上玩一个游戏,黑板上写了n个正整数a1, a2, ..., an,游戏的规则是这样的:1. Alice占有先手主动权.2. 每个人可以选取一 ...
- UVALive 6168 Fat Ninjas --二分小数+搜索
题意:一个NxN的网格地板,有一些激光束从天花板垂直射向地面的某个网格,一个圆要安全地从左走到右,不碰到上边界,下边界以及激光束,问这个圆的直径最大能达到多大. 分析:可以二分直径,关键在check函 ...
- Android_firstClass
一个Project 创建后,大概的文件目录如下:在Android Studio每个Project,相当于Eclipse 的WorkSpace:每个Module(上图的app 目录)相当于Eclipse ...
- MyEclipse无法启动调试:Cannot connect to VM
MyEclipse无法启动调试:Cannot connect to VM 问题描述:Eclipse普通的Run模式没有问题,Debug模式却启动不了.换了Eclipse,MyEclipse,JDK都不 ...