进程可以包含多个线程,但是主线程只有一个,如果遇到繁琐的任务,可以开辟线程,开辟出来的线程叫做分线程
static void Main(string[] args) { //Main是主函数
Console.WriteLine("这句代码是在主线程中执行的");
//设置主线程名称
//Thread.CurrentThread 获取当前正在运行的线程
Thread.CurrentThread.Name = "Main";
Console.WriteLine(Thread.CurrentThread.Name); //Main
}
创建分线程的第一种方式
//创建线程的第一种方法设置线程委托
//1、设置线程委托(目的是为了告诉线程应该执行的耗时任务是谁)
ThreadStart childref = new ThreadStart(Promothod);
//2、把委托交给Thread
Thread childThread = new Thread(childref);
//3、可以分线程Thread分配一个名字,便于甄别
childThread.Name = "分线程1";
// Start 执行分线程
childThread.Start();
//定义一个函数
public static void Promothod() {
Console.WriteLine(Thread.CurrentThread.Name);
}
创建线程的第二种方式(箭头函数)
Thread thread2 = new Thread(() => {
Console.WriteLine("这是分线程2");
});
thread2.Start();
Sleep 线程休眠
Thread thread3 = new Thread(() => {
Console.WriteLine("这是分线程3,开始睡觉~耗时3秒");
//Sleep 线程休眠的毫秒数
Thread.Sleep(3000);
Console.WriteLine("睡醒了,开始敲代码了。");
});
thread3.Start();
//Abort 线程启动之后,使用完毕最好要销毁掉
thread3.Abort();
带有参数的委托类(使用线程委托方法)
//带有参数的委托类,参数必须是object类型,否则会报错
ParameterizedThreadStart ptstart = new ParameterizedThreadStart(Promothod2);
Thread thread4 = new Thread(ptstart);
thread4.Start("我是传进来的参数");
//定义一个参数是对象类型的函数
public static void Promothod2(object data) {
Console.WriteLine("传进来的参数:" + data);
}
带有参数的委托类(使用箭头函数方法)
Thread thread5 = new Thread((e) => {
Console.WriteLine(e);
});
thread5.Start(abc);
Join 阻塞线程
static void Main(string[] args) { //Main是主函数
//创建一个线程并执行
Thread thread6 = new Thread((e) => {
Console.WriteLine(e);
Thread.Sleep(6000)
});
thread6.Start(abc);
// Join 阻塞当前依赖的主线程,直到分线程执行完毕
thread6.Join();
//下面这行代码需要等到上面的线程运行完才会执行
Console.WriteLine("这句代码是在主线程中执行的");
}
线程抢占是由操作系统负责的,它决定哪个线程将获得 CPU 的执行时间。
如果两个线程同时对某一个资源进行访问,就可能出现线程抢占的问题。
线程锁是一种用于控制多个线程对共享资源进行访问的机制,以防止并发访问时出现数据不一致或错误的情况。使用锁能够确保在某一时刻只有一个线程可以访问共享资源,其他线程需要等待获取锁的释放才能继续执行。
线程锁抢票案例、
static int p = 1;
static readonly object locker = new object(); //当做一个锁
static void Main(string[] args) {
Thread thread1 = new Thread(Go);
thread1.Name = "张三";
thread1.Start();
Thread thread2 = new Thread(Go);
thread2.Name = "李四";
thread2.Start();
}
static void Go() {
//lock 语句获取给定对象的互斥 lock,执行语句块,然后释放 lock。 持有 lock 时,持有 lock 的线程可以再次获取并释放 lock。 阻止任何其他线程获取 lock 并等待释放 lock。 lock 语句可确保在任何时候最多只有一个线程执行其主体。
lock (locker) {
Console.WriteLine(Thread.CurrentThread.Name + "进来了,此时票数为:" + p + "张。");
if (p == 1) {
Console.WriteLine(Thread.CurrentThread.Name + "买到了" + p + "张票。");
} else {
Console.WriteLine(Thread.CurrentThread.Name + "没抢到,继续加油!");
}
}
p--;
}
Task特点:
Task
允许你执行长时间运行的操作,而不会阻塞主线程,从而提高程序的响应性。Task
可以由系统自动分配到线程池中的线程,也可以使用 TaskScheduler
进行任务调度和控制。Task
可以进行任务链式调用,允许一个任务完成后自动开始另一个任务,形成任务流水线。创建一个简单的Task对象
var task1 = new Task(() => {
Console.WriteLine("task分线程任务");
});
//实例化Task之后也需要调用Start才会执行
task1.Start();
创建一个带参数的Task对象
//携带参数,参数放在箭头函数的后面
Task task2 = new Task(e => {
Console.WriteLine(e);
}, 123);
task2.Start();
Run 在线程池上运行的指定工作排队,并返回该工作的任务或Task
//带有返回类型,Run适合多线程的操作
//使用Run方法就不需要Start在开启了
Task task = Task.Run(() => {
for (int i = 0; i < 10; i++) {
Console.WriteLine("task:{0}", (i + 1));
Thread.Sleep(500);
}
});
Task task2 = Task.Run(() => {
//Wait 只有等到第一个执行完毕之后才会执行第二个
task.Wait();
Console.WriteLine("task2开始执行了。");
});
//Task.WaitAll(task);//等待所有任务都执行完毕(大家一起执行)
//Task.WaitAny(task);//任意一个任务完成都会向下执行(竞速)
//task3.ContinueWith(t => { }); //上一个task完成之后会自动启动下一个task,实现task的延续
Task task5 = Task.Run(() => {
Console.WriteLine("5555");
});
task5.ContinueWith(t => {
Task.Run(() => {
Console.WriteLine("test");
});
});
线程池(ThreadPool):线程和线程池都是进行多线程操作的。线程池可以用来理解为是保存线程的一个容器。
//WaitCallback 是一个委托类型,它用于表示一个在线程池中执行的方法。
WaitCallback waitCallback = new WaitCallback(ProgramMothod);
//创建线程池:在程序创建线程来执行任务的时候,如果交给了线程池操作,那么线程即使执行完毕了也不会被销毁,而是被挂起了,等待下一个任务被激活。
//当线程池里面的资源不够用的时候,会实例化一个新线程,来执行。默认线程池里面的线程是会反复利用的。
//标准写法
ThreadPool.QueueUserWorkItem(waitCallback);
//简写1(箭头函数)
WaitCallback w2 = arg => Console.WriteLine("do something2");
ThreadPool.QueueUserWorkItem(w2);
//简写2(箭头函数)
ThreadPool.QueueUserWorkItem(e => {
Console.WriteLine("dosimething3");
});
//传参的写法(参入一个对象作为参数)
//第一个参数表示委托,第二个参数是参数对象,用于给委托传递数据
ThreadPool.QueueUserWorkItem((e) => {
People p = e as People; //这里使用了 as 关键字转义类型
Console.WriteLine(p.Id);
}, new People() { Id = 100 });
//定义一个类型为object类型的方法
public static void ProgramMothod(object a) {
Console.WriteLine("do something");
}
//定义一个类
public class People {
public int Id;
}
ManualResetEvent 多任务通信,用来控制多个线程之间的执行顺序以及时间
ManualResetEvent和线程池(ThreadPool)配合使用
ManualResetEvent mreset = new ManualResetEvent(false);
// ManualResetEvent和线程池(ThreadPool)配合使用
ThreadPool.QueueUserWorkItem(m => {
Thread.Sleep(2000);
Console.WriteLine("准备干一些工作");
mreset.Set(); // Set 发送命令,如果有mreset.WaitOne方法在,那么收到Set命令之后,后续的代码才会执行
});
// WaitOne 阻塞线程
//上面执行完才会执行下面的
mreset.WaitOne();
Console.WriteLine("waitOne收到信号才会打印");
ManualResetEvent和Task配合使用
//下面将用于个打印奇数和偶数的案例演示和Task配合使用的情况
ManualResetEvent je = new ManualResetEvent(false);
Task task = new Task(e => {
//打印所有的奇数
Js(e);
je.Set(); //发送命令
}, 10);
ManualResetEvent oe = new ManualResetEvent(false);
Task task2 = new Task(e => {
//打印所有的偶数
Os(e);
oe.Set();//发送命令
}, 10);
task.Start();
task2.Start();
oe.WaitOne();
je.WaitOne();
Console.WriteLine("奇数和偶数都打印完毕了");
//定义一个打印奇数的方法
public static void Js(object e) {
int x = Convert.ToInt32(e);
for (int i = 1; i <= x; i += 2) {
Console.WriteLine("奇数:" + i);
Thread.Sleep(500);
}
}
//定义一个打印偶数的方法
public static void Os(object e) {
int x = Convert.ToInt32(e);
for (int i = 0; i <= x; i += 2) {
Console.WriteLine("偶数:" + i);
Thread.Sleep(500);
}
}
ThreadPool、Thread、Task三者 都是用于多线程编程的类或机制
三者的区别
ThreadPool
更适合处理较小的异步操作,而不需要手动创建和管理线程。Thread
提供了对线程的直接控制,但需要手动管理线程的生命周期。Task
是一个高级抽象,结合 async/await
更方便编写和管理异步代码,它可以利用线程池或后台线程来执行任务。使用工厂模式创建Task
//工厂模式创建Task
var task3 = Task.Factory.StartNew(() => {
Console.WriteLine("这是使用Factory工厂模式创建的线程1");
});
Task.Factory.StartNew(() => {
Console.WriteLine("这是使用Factory工厂模式创建的线程2");
});
//带有返回类型的Task
Task<int> task4 = Task.Factory.StartNew<int>(() => {
Console.WriteLine("这是使用Factory工厂模式创建的线程3");
return 100;
});
//使用 Result 方法获取返回的值
Console.WriteLine(task4.Result);//100
//带有返回类型和参数:数字100是匿名函数的参数,return 200 表示匿名函数的返回值
Task.Factory.StartNew<int>(e => {
Console.WriteLine("这是使用Factory工厂模式创建的线程4");
return 200;
}, 100);