什么是线程?
线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。
进程和线程的区别?
进程(Process)是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。
线程的操作
了解System.Threading.Thread类
- Thread(ParameterizedThreadStart)
- Thread(ParameterizedThreadStart,Int32)
- Thread(ThreadStart)
- Thread(ThreadStart,Int32)
ParameterizedThreadStart委托的参数类型必须是Object的。如果使用的是不带参数的委托,不能使用带参数的Start方法运行线程,否则系统会抛出异常。但使用带参数的委托,可以使用thread.Start()来运行线程,这时所传递的参数值为null。
线程定义
Thread thread=new Thread(new ThreadStart(method));//创建线程
thread.Start(); //启动线程
线程常用属性
- CurrentContext
- CurrentThread
- ExecutionContext
- IsBackground
- IsAlive
- IsBackground
- IsThreadPoolThread
- ManagedThreadId
- Name
- Priority
- ThreadState
线程优先级
- 优先级越高表示CPU分配给该线程的时间片越多,执行时间就多
- 优先级越低表示CPU分配给该线程的时间片越少,执行时间就少
常用方法
- Abort() 终止线程
- GetDomain() 返回当前线程正在其中运行的当前域
- GetDomainId() 返回当前线程正在其中运行的当前域Id
- Interrupt() 中断处于WaitSleepJoin线程状态的线程
- Join()已重载,阻塞调用线程,知道某个线程终止时为止
- Resume()继续运行挂起的线程
- Start()执行本线程
- Suspend()挂起当前线程,如果当前线程已属于挂起状态则此不起作用
- Sleep()把正在运行的线程挂起一段时间
多线程编程
优点:
可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。
缺点:
- 线程也是程序,所以线程需要占用内存,线程越多,占用内存也越多。
- 多线程需要协调和管理,所以需要占用CPU时间以便跟踪线程。
- 线程之间对共享资源的访问会相互影响,必须解决争用共享资源的问题。
- 线程太多会导致控制太复杂,最终可能造成很多程序缺陷。
线程同步与安全性
lock(object obj)
- lock的是必须是引用类型的对象,string类型除外。
- lock推荐的做法是使用静态的、只读的、私有的对象。
- 保证lock的对象在外部无法修改才有意义,如果lock的对象在外部改变了,对其他线程就会畅通无阻,失去了lock的意义。
Monitor
- Enter(Object) 在指定对象上获取排他锁。
- Exit(Object) 释放指定对象上的排他锁。
- Pulse 通知等待队列中的线程锁定对象状态的更改。
- PulseAll 通知所有的等待线程对象状态的更改。
- TryEnter(Object) 试图获取指定对象的排他锁。
- TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
- Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。
Mutex
mutex”是术语“互相排斥(mutually exclusive)”的简写形式,也就是互斥量。互斥量跟临界区中提到的Monitor很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂,因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。.Net中mutex由Mutex类来表示。
- WaitOne()
- ReleaseMutex()
线程池原理
.NET Framework的ThreadPool类提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。那么什么是线程池?线程池其实就是一个存放线程对象的“池子(pool)”,他提供了一些基本方法,如:设置pool中最小/最大线程数量、把要执行的方法排入队列等等。ThreadPool是一个静态类,因此可以直接使用,不用创建对象。
工作原理:
- 当线程池被创建后,里面就会创建5个空线程(和下限值相同)
- 当我们向线程池中排入一个任务后,就会有一个空线程接手该任务,然后运行起来,随着我们不断向线程池中排入任务,线程池中的空线程逐一接手任务并被执行。
- 随着任务的增加,在某一时刻任务数量会超出下限。当任务请求数量超出下限时,线程池并不会立即创建新线程,而是等待大约500毫秒左右,这么做的目的是看看在这段时间内是否有其他线程完成任务来接手这个请求,这样就可以避免因创建新线程而造成的消耗。如果这段时间内没有线程完成任务,就创建一个新线程去执行新任务。
- 当任务数量超过下限后,每排入一个新任务,就会增加一个新线程,这段期间,任务和线程数量都持续增加,直至线程数量达到上限值为止。
- 当线程数量达到上限时,继续增加任务,线程数量将不再增加,比如你向线程池中排入100个任务,则只有25个进入线程池(和上限相同),另外75个在线程池外排队等待。当线程池中的某个线程完成任务后,并不会被终止,而是从等待队列中选择一个任务继续执行,这样就减少了因创建和销毁线程而消耗的时间。
- 当排入所有的任务后,随着线程池内的任务呗逐步完成,线程池外部等候的任务呗逐步调入线程池,任务的数量逐步减少,但线程的数量保持恒定,始终和上限值相同。
- 随着任务被逐步完成,总有某一时刻,任务数量会小于上限值,这时线程池内多余的线程会在空闲2分钟后被释放并回收相关资源。线程数目逐步减少,直到达到下限值为止。
- 当任务数量减小到下限值之下时,线程池中的线程数目保持不变(始终和下限值相同),其中一部分在执行任务,另一部分处于空运行状态。
- 当所有的任务都完成后,线程池恢复初始状态,运行5个空线程。
实例1
效果图
代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 多线程信号量
{
class Program
{
/*
* Semaphore,是负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。也是操作系统中用于控制进程同步互斥的量。
* Semaphore常用的方法有两个WaitOne()和Release(),Release()的作用是退出信号量并返回前一个计数,而WaitOne()则是阻止当前线程,直到当前线程的WaitHandle 收到信号。这里我举一个例子让大家更容易理解:当我们这样实例化Semaphore时候
Semaphore sema = new Semaphore(x,y)
有一队人排队上洗手间,人就相当于线程,x为还剩余的位置数量,y为总的位置数量。
WaitOne()方法就相当于人在等待洗手间位置的行为,而Release()方法就相当于一个人从洗手间出来的行为,这里再假设x和y都为5,说明开始的时候洗手间有5个空位置,且总共只有5个位置,当一队超过5个人的队伍要上洗手间的就排队,首先WaitOne()方法等待,发现有空位就依次进去,每进去一个空位减一,直到进去5之后个没有空位,这时候后面的人就一直等待,直到进去的人从洗手间出来Release()方法,空位加一,在等待WaitOne()方法的人发现有空位又进去一个空位减一……如此循环往复。
*/
static void Main(string[] args)
{
Queue<Thread> list = new Queue<Thread>();
Semaphore sema = new Semaphore(5, 5);
for (int i = 0; i < 9; i++)
{
Thread thread = new Thread((x) =>
{
sema.WaitOne();
Console.WriteLine($"{x}正在上厕所...");
Thread.Sleep(2000);
Console.WriteLine($"{x}用完了出来...");
sema.Release();
});
thread.Name = $"编号{i}";
thread.Start(thread.Name);
list.Enqueue(thread);//把所有线程都加入到一个队列中
}
foreach (var thread in list)
{
thread.Join();
}
Console.WriteLine("所有人都上完厕所了!!!");
Console.ReadKey();
}
}
}
实例2
效果图
代码:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using DevExpress.Utils;
using ThreadState = System.Threading.ThreadState;
namespace 多线程
{
public partial class Form1 : DevExpress.XtraEditors.XtraForm
{
public Form1()
{
InitializeComponent();
//取消线程间的检查(不安全)
//Control.CheckForIllegalCrossThreadCalls = false;
}
/*
* 只有当所有的前台线程都退出,进程才结束
* 所有的前台线程都结束,后台线程自动停止
*/
//多线程
private void btnMulty_Click(object sender, EventArgs e)
{
Thread thread = new Thread(CountNum);
thread.IsBackground = true;//设置为后台线程
thread.Start();
}
void CountNum()
{
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < int.MaxValue; i++)
{
}
watch.Stop();
MessageBox.Show(watch.ElapsedMilliseconds.ToString());
}
//单线程
private void btnSingle_Click(object sender, EventArgs e)
{
ChangeText();
}
/*
* 线程是由CPU
*/
void ChangeText()
{
//计时器
Stopwatch watch = new Stopwatch();
watch.Start();
for (int i = 0; i < 10000; i++)
{
//只允许只有一个线程才能执行
lock (this)
{
var num = Convert.ToInt32(txtNum.Text);
Console.WriteLine($"{Thread.CurrentThread.Name},num={num},i={i}");
num++;
txtNum.Invoke((Action)(() =>
{
txtNum.Text = num.ToString();
}));
}
}
watch.Stop();
MessageBox.Show(watch.ElapsedMilliseconds.ToString());
}
//线程同步问题
private void btnLock_Click(object sender, EventArgs e)
{
Thread th1 = new Thread(ChangeText);
th1.Name = "th1";
th1.IsBackground = true;
th1.Start();
Thread th2 = new Thread(ChangeText);
th2.Name = "th2";
th2.IsBackground = true;
th2.Start();
}
void Show(object obj)
{
StudentInfo info = obj as StudentInfo;
MessageBox.Show($"Name={info.Name},id={info.Id}");
}
//带参数执行
private void btnParam_Click(object sender, EventArgs e)
{
//Thread thread = new Thread((str) =>
//{
// /*
// * 可以写点别的业务逻辑
// */
// MessageBox.Show(str.ToString());
//});
// thread.IsBackground = true;
// thread.Start(txtParam.Text);
/*
* 单个参数传递
*/
//ParameterizedThreadStart parameterized = new ParameterizedThreadStart(Show);
//Thread th = new Thread(parameterized);
//th.IsBackground = true;
//th.Start(txtParam.Text);
/*
* 传递多个参数
*
*/
StudentInfo info = new StudentInfo()
{
Id = 10,
Name = txtParam.Text
};
ParameterizedThreadStart parameterized = new ParameterizedThreadStart(Show);
Thread th = new Thread(parameterized);
th.IsBackground = true;
th.Start(info);
}
private Thread globalThread = null;
private int i = 0;
//开启线程
private void btnStart_Click(object sender, EventArgs e)
{
globalThread = new Thread(() =>
{
for (int j = 0; j < 100000; j++)
{
progressBar1.Invoke((Action)(() =>
{
progressBar1.Value = i++;
}));
}
});
globalThread.IsBackground = true;
globalThread.Start();
}
//线程挂起
private void btnSupend_Click(object sender, EventArgs e)
{
globalThread.Suspend();
}
//唤醒线程
private void btnAwake_Click(object sender, EventArgs e)
{
globalThread.Resume();
}
//终止线程
private void simpleButton1_Click(object sender, EventArgs e)
{
if (globalThread.ThreadState != ThreadState.Suspended)
{
globalThread.Abort();
}
}
//线程池
private void btnTask_Click(object sender, EventArgs e)
{
Task.Run(new Action(ChangeText));
Task.Run(() =>
{
//可以在这里写代码
//业务逻辑
});
}
private void btnSearch_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
WaitDialogForm frm = new WaitDialogForm("查询","正在查询....");
frm.Show();//ShowDialog会阻断当前线程的执行,直到Dialog结束
btnSearch.Invoke((Action) (() =>
{
btnSearch.Text = "正在查询...";
btnSearch.Enabled = false;
}));
/*
* 暂停3秒,表示正在查询
*/
Thread.Sleep(3000);
frm.Close();
btnSearch.Invoke((Action)(() =>
{
btnSearch.Text = "查询";
btnSearch.Enabled = true;
}));
});
thread.IsBackground = true;
thread.Start();
}
private void btnSleep_Click(object sender, EventArgs e)
{
Thread.Sleep(5000);
}
}
public class StudentInfo
{
public string Name { get; set; }
public int Id { get; set; }
}
}
Program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using DevExpress.UserSkins;
using DevExpress.Skins;
using DevExpress.LookAndFeel;
namespace 多线程
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
BonusSkins.Register();
UserLookAndFeel.Default.SetSkinStyle("Sharp");
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
BonusSkins.Register();
Application.Run(new Form1());
}
}
}