这篇文章主要介绍了大家应该掌握的多线程编程,具有一定借鉴价值,需要的朋友可以参考下
毫无疑问,多线程在各种编程语言中都占有比较重要的一个席位。不管你是初学者,还是资深的老司机,多线程是在学习,面试和工作中都要经常被提及的一个话题,下面我们就来看一看具体的相关内容。
1、多线程编程必备知识
1.1 进程与线程的概念
当我们打开一个应用程序后,操作系统就会为该应用程序分配一个进程ID,例如打开QQ,你将在任务管理器的进程选项卡看到QQ.exe进程,如下图:
using System;
using System.Threading;
namespace 多线程1
{
internal class Program
{
private static void Main(string[] args)
{
var backThread = new Thread(Worker);
backThread.IsBackground = true;
backThread.Start();
Console.WriteLine("从主线程退出");
Console.ReadKey();
}
private static void Worker()
{
Thread.Sleep(1000);
Console.WriteLine("从后台线程退出");
}
}
}
以上代码先通过Thread类创建了一个线程对象,然后通过设置IsBackground属性来指明该线程为后台线程。如果不设置这个属性,则默认为前台线程。接着调用了Start的方法,此时后台线程会执行Worker函数的代码。所以在这个程序中有两个线程,一个是运行Main函数的主线程,一个是运行Worker线程的后台线程。由于前台线程执行完毕后CLR会无条件地终止后台线程的运行,所以在前面的代码中,若启动了后台线程,则主线程将会继续运行。主线程执行完后,CLR发现主线程结束,会终止后台线程,然后使整个应用程序结束运行,所以Worker函数中的Console语句将不会执行。所以上面代码的结果是不会运行Worker函数中的Console语句的。
可以使用Join函数的方法,确保主线程会在后台线程执行结束后才开始运行。
using System;
using System.Threading;
namespace 多线程1
{
internal class Program
{
private static void Main(string[] args)
{
var backThread = new Thread(Worker);
backThread.IsBackground = true;
backThread.Start();
backThread.Join();
Console.WriteLine("从主线程退出");
Console.ReadKey();
}
private static void Worker()
{
Thread.Sleep(1000);
Console.WriteLine("从后台线程退出");
}
}
}
以上代码调用Join函数来确保主线程会在后台线程结束后再运行。
如果你线程执行的方法需要参数,则就需要使用new Thread的重载构造函数Thread(ParameterizedThreadStart).
using System;
using System.Threading;
namespace 多线程1
{
internal class Program
{
private static void Main(string[] args)
{
var backThread = new Thread(new ParameterizedThreadStart(Worker));
backThread.IsBackground = true;
backThread.Start("Helius");
backThread.Join();
Console.WriteLine("从主线程退出");
Console.ReadKey();
}
private static void Worker(object data)
{
Thread.Sleep(1000);
Console.WriteLine($"传入的参数为{data.ToString()}");
}
}
}
执行结果为:
using System;
using System.Threading;
namespace 多线程2
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"主线程ID={Thread.CurrentThread.ManagedThreadId}");
ThreadPool.QueueUserWorkItem(CallBackWorkItem);
ThreadPool.QueueUserWorkItem(CallBackWorkItem,"work");
Thread.Sleep(3000);
Console.WriteLine("主线程退出");
Console.ReadKey();
}
private static void CallBackWorkItem(object state)
{
Console.WriteLine("线程池线程开始执行");
if (state != null)
{
Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId},传入的参数为{state.ToString()}");
}
else
{
Console.WriteLine($"线程池线程ID={Thread.CurrentThread.ManagedThreadId}");
}
}
}
}
结果为:
using System;
using System.Threading;
namespace 多线程3
{
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("主线程运行");
var cts = new CancellationTokenSource();
ThreadPool.QueueUserWorkItem(Callback, cts.Token);
Console.WriteLine("按下回车键来取消操作");
Console.Read();
cts.Cancel();
Console.ReadKey();
}
private static void Callback(object state)
{
var token = (CancellationToken) state;
Console.WriteLine("开始计数");
Count(token, 1000);
}
private static void Count(CancellationToken token, int count)
{
for (var i = 0; i < count; i++)
{
if (token.IsCancellationRequested)
{
Console.WriteLine("计数取消");
return;
}
Console.WriteLine($"计数为:{i}");
Thread.Sleep(300);
}
Console.WriteLine("计数完成");
}
}
}
结果为:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 线程同步
{
class Program
{
private static int tickets = 100;
static object globalObj=new object();
static void Main(string[] args)
{
Thread thread1=new Thread(SaleTicketThread1);
Thread thread2=new Thread(SaleTicketThread2);
thread1.Start();
thread2.Start();
Console.ReadKey();
}
private static void SaleTicketThread2()
{
while (true)
{
try
{
Monitor.Enter(globalObj);
Thread.Sleep(1);
if (tickets > 0)
{
Console.WriteLine($"线程2出票:{tickets--}");
}
else
{
break;
}
}
catch (Exception)
{
throw;
}
finally
{
Monitor.Exit(globalObj);
}
}
}
private static void SaleTicketThread1()
{
while (true)
{
try
{
Monitor.Enter(globalObj);
Thread.Sleep(1);
if (tickets > 0)
{
Console.WriteLine($"线程1出票:{tickets--}");
}
else
{
break;
}
}
catch (Exception)
{
throw;
}
finally
{
Monitor.Exit(globalObj);
}
}
}
}
}
在以上代码中,首先额外定义了一个静态全局变量globalObj,并将其作为参数传递给Enter方法。使用了Monitor锁定的对象需要为引用类型,而不能为值类型。因为在将值类型传递给Enter时,它将被先装箱为一个单独的毒香,之后再传递给Enter方法;而在将变量传递给Exit方法时,也会创建一个单独的引用对象。此时,传递给Enter方法的对象和传递给Exit方法的对象不同,Monitor将会引发SynchronizationLockException异常。
3.3线程同步技术存在的问题
(1)使用比较繁琐。要用额外的代码把多个线程同时访问的数据包围起来,还并不能遗漏。
(2)使用线程同步会影响程序性能。因为获取和释放同步锁是需要时间的;并且决定那个线程先获得锁的时候,CPU也要进行协调。这些额外的工作都会对性能造成影响。
(3)线程同步每次只允许一个线程访问资源,这会导致线程堵塞。继而系统会创建更多的线程,CPU也就要负担更繁重的调度工作。这个过程会对性能造成影响。
下面就由代码来解释一下性能的差距:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 线程同步2
{
class Program
{
static void Main(string[] args)
{
int x = 0;
const int iterationNumber = 5000000;
Stopwatch stopwatch=Stopwatch.StartNew();
for (int i = 0; i < iterationNumber; i++)
{
x++;
}
Console.WriteLine($"不使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
stopwatch.Restart();
for (int i = 0; i < iterationNumber; i++)
{
Interlocked.Increment(ref x);
}
Console.WriteLine($"使用锁的情况下花费的时间:{stopwatch.ElapsedMilliseconds}ms");
Console.ReadKey();
}
}
}
执行结果:
总结
以上就是本文关于大家应该掌握的多线程编程的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!
本文标题为:大家应该掌握的多线程编程
- .NET CORE DI 依赖注入 2023-09-27
- Unity Shader实现模糊效果 2023-04-27
- user32.dll 函数说明小结 2022-12-26
- Oracle中for循环的使用方法 2023-07-04
- Unity3D实现渐变颜色效果 2023-01-16
- 如何使用C# 捕获进程输出 2023-03-10
- C# 使用Aspose.Cells 导出Excel的步骤及问题记录 2023-05-16
- c# 模拟线性回归的示例 2023-03-14
- WPF使用DrawingContext实现绘制刻度条 2023-07-04
- 在C# 8中如何使用默认接口方法详解 2023-03-29