在C#中,多线程可以让程序同时执行多个任务,避免耗时操作阻塞主线程,提升程序的响应速度和执行效率,Thread类是最基础的多线程实现载体,位于System.Threading命名空间下。

Thread类的基础使用
创建和启动线程
使用Thread类实现多线程的第一步是创建线程对象,需要传入一个ThreadStart委托或者ParameterizedThreadStart委托作为线程执行的方法,然后调用Start方法启动线程。
以下是一个简单的无参数线程创建示例:
using System;
using System.Threading;
namespace ThreadDemo
{
class Program
{
static void Main(string[] args)
{
// 创建线程对象,传入无参数的线程执行方法
Thread thread = new Thread(PrintMessage);
// 启动线程
thread.Start();
// 主线程执行的内容
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"主线程执行:{i}");
Thread.Sleep(100);
}
Console.ReadKey();
}
// 线程执行的无参数方法
static void PrintMessage()
{
for (int i = 0; i < 5; i++)
{
Console.WriteLine($"子线程执行:{i}");
Thread.Sleep(100);
}
}
}
}
传递参数给线程
如果线程执行的方法需要参数,可以使用ParameterizedThreadStart委托,参数类型为object,启动线程时通过Start方法传入参数,使用时需要做类型转换。
using System;
using System.Threading;
namespace ThreadDemo
{
class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(PrintWithParam);
// 启动线程时传入参数
thread.Start("这是传递给子线程的参数");
Console.ReadKey();
}
static void PrintWithParam(object param)
{
string message = param as string;
if (!string.IsNullOrEmpty(message))
{
Console.WriteLine($"子线程接收到参数:{message}");
}
}
}
}
常用属性和方法
Thread类提供了很多实用的属性和方法,用于控制线程的状态和行为,常见的如下:
- Name:设置或获取线程的名称,方便调试时区分不同线程
- IsAlive:判断线程是否还在运行
- Priority:设置线程的优先级,优先级高的线程会获得更多的CPU执行时间
- Join():阻塞当前线程,直到目标线程执行完毕
- Sleep(int millisecondsTimeout):让当前线程暂停指定的毫秒数
- Abort():已过时,强制终止线程,不建议使用,容易导致资源未释放等问题
以下是Join方法的使用示例:
using System;
using System.Threading;
namespace ThreadDemo
{
class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(DoWork);
thread.Name = "工作线程";
thread.Start();
Console.WriteLine("主线程等待子线程执行完毕");
// 阻塞主线程,直到子线程执行完成
thread.Join();
Console.WriteLine("子线程执行完毕,主线程继续执行");
Console.ReadKey();
}
static void DoWork()
{
Console.WriteLine($"{Thread.CurrentThread.Name} 开始执行");
Thread.Sleep(2000);
Console.WriteLine($"{Thread.CurrentThread.Name} 执行完毕");
}
}
}
多线程的线程同步问题
当多个线程同时访问同一个共享资源时,可能会出现数据不一致的问题,这就是线程安全问题,需要通过线程同步来解决。
问题示例
下面的代码模拟两个线程同时对一个共享变量做累加操作,正常情况下两个线程各累加1000次,结果应该是2000,但是实际运行可能会出现小于2000的情况:
using System;
using System.Threading;
namespace ThreadDemo
{
class Program
{
static int count = 0;
static void Main(string[] args)
{
Thread thread1 = new Thread(AddCount);
Thread thread2 = new Thread(AddCount);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine($"最终count的值:{count}");
Console.ReadKey();
}
static void AddCount()
{
for (int i = 0; i < 1000; i++)
{
count++;
}
}
}
}
使用lock关键字解决同步问题
lock关键字可以限制一段代码同时只能被一个线程访问,需要传入一个引用类型的对象作为锁对象,通常建议使用private static的object对象作为锁对象。
using System;
using System.Threading;
namespace ThreadDemo
{
class Program
{
static int count = 0;
// 定义锁对象
private static readonly object lockObj = new object();
static void Main(string[] args)
{
Thread thread1 = new Thread(AddCount);
Thread thread2 = new Thread(AddCount);
thread1.Start();
thread2.Start();
thread1.Join();
thread2.Join();
Console.WriteLine($"最终count的值:{count}");
Console.ReadKey();
}
static void AddCount()
{
for (int i = 0; i < 1000; i++)
{
// 加锁,保证同一时间只有一个线程执行count++操作
lock (lockObj)
{
count++;
}
}
}
}
}
其他同步方式
除了lock关键字,C#还提供了其他线程同步方式,比如Monitor类、Mutex类、Semaphore类等,开发者可以根据实际场景选择合适的同步方式。
使用注意事项
- 不要随意使用Thread.Abort方法终止线程,容易导致程序状态异常,建议通过设置标志位让线程自行退出
- 线程的创建和销毁是有开销的,频繁创建销毁线程会影响性能,实际开发中可以考虑使用线程池ThreadPool或者Task类
- 锁对象的选择要合理,不要使用public的对象作为锁,避免被外部代码锁定导致死锁
- 多线程调试难度较高,开发时可以先编写单线程逻辑,验证正确后再改为多线程实现