C#基础——线程(线程池、线程锁、线程抢占、多线程)

发布时间:2023年12月22日

线程

进程(Process)是由操作系统分配资源并执行的一个独立的程序实,属于Windows的概念,进程结束就表示程序关闭了。

线程(Thread)是程序中执行的最小单位。一个线程代表了一个独立的执行流,可以独立运行,执行特定的任务。每个应用程序至少有一个线程,即主线程,用于执行主要的程序代码。

进程可以包含多个线程,但是主线程只有一个,如果遇到繁琐的任务,可以开辟线程,开辟出来的线程叫做分线程

static void Main(string[] args) { //Main是主函数
	Console.WriteLine("这句代码是在主线程中执行的");
    
    //设置主线程名称
    //Thread.CurrentThread 获取当前正在运行的线程
	Thread.CurrentThread.Name = "Main";
	Console.WriteLine(Thread.CurrentThread.Name); //Main
}

Thread 类 :表示一个操作系统级别的线程,允许你在应用程序中创建、控制和管理线程。

创建分线程的第一种方式

//创建线程的第一种方法设置线程委托
//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--;
}

Tack多线程

Task: 是用于表示可以异步执行的操作的类。它允许你在单独的线程或线程池中执行任务,而不必直接操作线程。

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);
文章来源:https://blog.csdn.net/qq_51810680/article/details/135158789
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。