在C#中,线程是使用Thread类处理的,该类在System.Threading命名空间中。
ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。如获取当前线程的id:
int a = Thread.CurrentThread.ManagedThreadId;
public void method()
{
int a = 1;
Thread.Sleep(1000);
}
public void method1(object a)
{
int b = (int)a;
Thread.Sleep(1000);
}
ThreadStart是一个委托,这个委托的定义为public delegate void ThreadStart();没有参数与返回值。
Thread thread = new Thread(new ThreadStart(method));
//或者 Thread thread = new Thread(method);
thread.Start();
thread.Join();
ParameterThreadStart委托定义为public delegate void ParameterizedThreadStart(object obj);有一个参数但是没有返回值。然后在Start函数中传递参数。
Thread thread1 = new Thread(new ParameterizedThreadStart(method1));
//或者 Thread thread4 = new Thread(method1);
thread1.Start(1);
使用线程类可以有多个参数与多个返回值。线程类的方法作为Thread对象的委托,
不需要使用Start传递参数。
public class MyThread//线程类
{
public int Parame { set; get; }//参数
public int Result { set; get; }//返回值
public void method()
{
int a = Parame;
Thread.Sleep(1000);
Result = Result + 1;
}
}
MyThread my = new MyThread();
my.Parame = 1;
my.Result = 2;
Thread thread3 = new Thread(my.method);
thread3.Start();
thread3.Join();
int a = my.Result;
使用匿名方法启动线程可以有多个参数和返回值,也可以自带参数。
委托类型 变量名 = delegate( 形参 ) { 逻辑处理语句 };
public delegate void ShowDelegate ();
ShowDelegate showDelegate3 = delegate (string strText)
{
Console.WriteLine(strText);
};
匿名委托不带参数,但使用上下文代码的参数
int b = 1;
int es = 0;
ThreadStart threadStart = new ThreadStart(delegate ()
{
int c = b;
Thread.Sleep(1000);
});
Thread thread5 = new Thread(threadStart);
//或者 Thread thread5 = new Thread(delegate ()
{
int c = b ;
es = c +1;
Thread.Sleep(1000);
});
thread5.Start();
thread5.Join();
匿名委托带参数,并使用上下文代码的参数
int b = 1;
int es = 0;
Thread thread5 = new Thread(delegate (object d)
{
int c = b + (int)d;
es = c +1;
Thread.Sleep(1000);
});
thread5.Start(2);
thread5.Join();
可以有多个参数和返回值,也可在Lambda表达式自带参数。
public delegate void ShowDelegate ();
ShowDelegate showDelegate4 = (string strText) => { Console.WriteLine(strText); };
Lambda表达式不带参数,但使用上下文代码的参数
int b = 1;
int es = 0;
Thread thread6 = new Thread(() =>
{
int c = b;
es = c + 1;
Thread.Sleep(1000);
});
thread6.Start();
thread6.Join();
Lambda表达式带参数,并使用上下文代码的参数
int b = 1;
int es = 0;
Thread thread6 = new Thread((object q) =>
{
int c = (int)q + b;
es = c + 1;
Thread.Sleep(1000);
});
thread6.Start(2);
thread6.Join();
BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值)。
public int method3(int a,int b)
{
int r = a + b;
Thread.Sleep(1000);
return r;
}
private delegate int NewTaskDelegate(int a,int b);
NewTaskDelegate task = method3;
IAsyncResult asyncResult = task.BeginInvoke(2,3,null,null);
int re = task.EndInvoke(asyncResult);
while (!asyncResult.IsCompleted)
{
Console.Write("*");
Thread.Sleep(100);
}
WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
public int method3(int a,int b)
{
int r = a + b;
Thread.Sleep(1000);
return r;
}
private delegate int NewTaskDelegate(int a,int b);
NewTaskDelegate task = method3;
IAsyncResult asyncResult = task.BeginInvoke(2,3, null, null);
while (!asyncResult.AsyncWaitHandle.WaitOne(1000, false))
{
Console.Write("*");
}
int re = task.EndInvoke(asyncResult);
BeginInvoke倒数第二个参数(MethodCompleted)是回调方法委托类型,他是回调方法的委托,此委托没有返回值,有一个IAsyncResult类型的参数,当method方法执行完后,系统会自动调用MethodCompleted方法。
最后一个参数(my)需要向MethodCompleted方法中传递一些值,一般可以传递被调用方法的委托,这个值可以使用IAsyncResult.AsyncState属性获得。
public int method3(int a,int b)
{
int r = a + b;
Thread.Sleep(1000);
return r;
}
private delegate int NewTaskDelegate(int a,int b);
private static void MethodCompleted(IAsyncResult asyncResult)
{
(asyncResult.AsyncState as NewTaskDelegate).EndInvoke(asyncResult);
}
NewTaskDelegate task = method3;
IAsyncResult asyncResult = task.BeginInvoke(2,3, MethodCompleted, task);
public void Start()
Thread thread2 = new Thread(method);
thread2.Start();
thread2.Join();
public void Suspend()
public void Resume()
public void Join()
Thread thread2 = new Thread(method);
thread2.Start();
thread2.Join();
public void Abort()
只有所有的前台线程都结束,应用程序才能结束。默认情况下创建的线程都是前台线程。
只要所有的前台线程结束,后台线程自动结束。通过Thread.IsBackground设置后台线程。必须在调用Start方法之前设置线程的类型,否则一旦线程运行,将无法改变其类型。通过BeginXXX方法运行的线程都是后台线程
排它锁用于确保同一时间只允许一个线程执行指定的代码段。主要的两个排它锁构造是lock和Mutex(互斥体)。其中lock更快,使用也更方便。而Mutex的优势是它可以跨进程的使用。
同一时间只有一个线程可以锁定同步对象(这里指_locker),并且其它竞争锁的线程会被阻塞,直到锁被释放。如果有多个线程在竞争锁,它们会在一个“就绪队列(ready queue)”中排队,并且遵循先到先得的规则。
优点:速度快,性能好
缺点:不能跨进程
语法
lock(expression) statement_block
其中expression代表你希望跟踪的对象,通常是对象引用。
如果你想保护一个类的实例,一般地,你可以使用this;
public partial class MainWindow : Window
{
public int val2 { get; set; } = 1;
public void method1()
{
lock (this)
{
val2 = 3;
}
Thread.Sleep(1000);
}
}
如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了。
class ThreadSafe
{
static object _locker = new object();
static int _val1, _val2;
static void Go()
{
lock (_locker)
{
if (_val2 != 0)
Console.WriteLine (_val1 / _val2);
_val2 = 0;
}
}
}
而statement_block就是互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。
延伸:C# 的lock语句是一个语法糖,它其实就是使用了try / finally来调用Monitor.Enter与Monitor.Exit方法。上面的lock代码等价于:
Monitor.Enter (_locker);
try
{
if (_val2 != 0) Console.WriteLine (_val1 / _val2);
_val2 = 0;
}
finally { Monitor.Exit (_locker); }
使用Mutex类时,可以调用WaitOne方法来加锁,调用ReleaseMutex方法来解锁。关闭或销毁Mutex会自动释放锁。。
优点:可以跨越多个进程工作。
缺点:没有竞争的情况下,获取并释放Mutex需要几微秒的时间,大约比lock慢 50 倍。
Mutex(Boolean)
参数1:指示调用线程是否应具有互斥体的初始所有权。
如果参数为true,相当于调用了WaitOne加锁。除非本线程调用ReleaseMutex(),否则别的线程是获取不到锁的。
应用场景:同一进程的多线程互斥,就使用该构造函数,并且参数为false.
static Mutex mutex = new Mutex(false);
public static int method3(int a,int b)
{
mutex.WaitOne();
_val3 = a + b;
Thread.Sleep(1000);
mutex.ReleaseMutex();
return a;
}
Mutex(Boolean, String)
参数1:指示调用线程是否应具有互斥体的初始所有权
参数2:字符串是否为互斥体的名称
Mutex(Boolean, String, Boolean)
参数1:指示调用线程是否应具有互斥体的初始所有权
参数2:字符串是否为互斥体的名称
参数3:为了知道自己构造出来的互斥锁是不是已经存在,如果锁存在,那么createdNew的变为false,否则为true
应用场景:确保只运行一个程序实例。
bool runone;
System.Threading.Mutex run = new System.Threading.Mutex(true, "jiaao_test", out runone);
if (runone)
{
run.ReleaseMutex();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
else
{
MessageBox.Show("已经运行了一个实例了。");
}
Semaphore、SemaphoreSlim以及读写锁
初始化时,可以指定一个初始计数值,这个值表示可以同时访问共享资源的线程数。
当线程调用WaitOne()方法时,如果信号量计数器大于0,则计数器减1,并允许线程继续执行;若计数器为0,则线程被阻塞并等待其他线程释放信号量。
通过调用Release()方法,可以增加信号量的计数值,从而允许一个或多个等待中的线程获取信号量并继续执行。
使用场景
1.资源池管理:例如,限制数据库连接池中同时活动的连接数,以防止过多连接导致系统过载。
2.互斥锁(Mutex)替代:当信号量初始化为1时,它相当于一个互斥锁,确保同一时间只有一个线程能访问临界区。
public Semaphore(int initialCount, int maximumCount)
第一个参数initialCount是初始信号量
第二个参数maximumCount是最大信号量
// 创建一个最多允许5个线程同时访问的信号量
Semaphore semaphore = new Semaphore(5, 5); // 第一个参数是初始信号量,第二个参数是最大信号量
void AccessResource()
{
// 尝试获取信号量
semaphore.WaitOne();
try
{
// 这里是临界区,只有拿到信号量的线程才能进入
// 执行对共享资源的操作...
}
finally
{
// 资源操作完毕后,释放信号量
semaphore.Release();
}
}
// 在多个线程中调用AccessResource()函数
构造 | 用途 | 跨进程 | 开销* |
(Monitor.Enter/Monitor.Exit) | 确保同一时间只有一个线程可以访问资源或代码 | - | 20ns |
1000ns | |||
(Framework 4.0 中加入) | 确保只有不超过指定数量的线程可以并发访问资源或代码 | - | 200ns |
1000ns | |||
(Framework 3.5 中加入) | 允许多个读线程和一个写线程共存 | - | 40ns |
(已过时) | - | 100ns |
事件等待句柄(event wait handle)用于信号同步。信号同步就是一个线程进行等待,直到它收到其它线程的通知的过程。
它们有三个成员:AutoResetEvent、ManualResetEvent以及CountdownEvent( Framework 4.0 中加入).
和c++ 的时间CEvent 一样的
构造函数AutoResetEvent(bool initialState)
initialState: 用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例.
false:无信号
true:有信号.
Reset ():将事件状态设置为无信号状态,导致线程阻止。
Set ():将事件状态设置为有信号状态,允许一个或多个等待线程继续。
WaitOne(): 阻止当前线程,直到收到信号。具体表现
如果 AutoResetEvent 处于无信号状态,WaitOne 方法将阻塞线程的执行。
如果 AutoResetEvent 处于有信号状态,WaitOne 方法将消耗该信号,并使 AutoResetEvent 重新进入无信号状态。
WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到有信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。如果在超时都没收到信号,返回值为false.如果在指定时间内收到信号,返回值为true.
using System;
using System.Threading;
class ProgramesetEvent
{
static AutoResetEvent producerEvent = new AutoResetEvent(false);
static AutoResetEvent consumerEvent = new AutoResetEvent(true);
static int item = 0;
static void Main()
{
Thread consumerThread = new Thread(ConsumeItems);
Thread producerThread= new Thread(ProduceItems);
producerThread.Start();
consumerThread.Start();
producerThread.Join();
consumerThread.Join();
Console.WriteLine("Finished");
}
static void ProduceItems()
{
for (int i = 0; i < 5; i++)
{
consumerEvent.WaitOne(); // 等待消费者消费物品
// 生产物品
item = i;
Console.WriteLine("Produced item: " + item);
producerEvent.Set(); // 发送信号给消费者
}
}
static void ConsumeItems()
{
for (int i = 0; i < 5; i++)
{
producerEvent.WaitOne(); // 等待生产者生产物品
// 消费物品
Console.WriteLine("Consumed item: " + item);
consumerEvent.Set(); // 发送信号给生产者
}
}
}
public ManualResetEvent(bool initialState)
最大特点:必须手动显式调用Reset才能设置回无信号状态。
函数和AutoResetEvent 一样的。
1. 触发方式:
AutoResetEvent:在调用 `Set` 方法后,只有一个等待线程会被唤醒并继续执行,然后 AutoResetEvent 会自动恢复为无信号状态。即每次调用 `Set` 方法只唤醒一个等待线程。
ManualResetEvent:在调用 `Set` 方法后,所有等待线程都会被唤醒并继续执行,直到显式调用 `Reset` 方法将 ManualResetEvent 设置回无信号状态为止。即每次调用 `Set` 方法会唤醒所有等待线程。
2. 状态重置:
AutoResetEvent:自动重置为无信号状态。即当一个线程等待信号并被唤醒后,AutoResetEvent 会自动将自身重置为无信号状态,以便下一个等待的线程能够继续等待。
ManualResetEvent:需要显式调用 `Reset` 方法将其重置为无信号状态。即 ManualResetEvent 会保持有信号状态,直到调用 `Reset` 方法将其置回无信号状态。
3. 等待方式:
AutoResetEvent:在调用 `WaitOne` 方法时,如果 AutoResetEvent 处于无信号状态,线程会被阻塞直到接收到信号。如果 AutoResetEvent 处于有信号状态,线程会消耗该信号并继续执行。
ManualResetEvent:在调用 `WaitOne` 方法时,如果 ManualResetEvent 处于无信号状态,线程会被阻塞直到接收到信号。即使 ManualResetEvent 处于有信号状态,线程也会消耗该信号并继续执行,而不会自动将其重置为无信号状态。 构造
构造 | 用途 | 跨进程 | 开销* |
使线程在收到其它线程信号时解除阻塞一次 | 1000ns | ||
使线程在收到其它线程信号时解除阻塞,并且不继续阻塞直到被复位 | 1000ns | ||
(Framework 4.0 中加入) | - | 40ns | |
(Framework 4.0 中加入) | 使线程在收到预定数量的信号时解除阻塞 | - | 40ns |
(Framework 4.0 中加入) | 实现线程执行屏障 | - | 80ns |
使线程阻塞,直到自定义条件被满足 | - | 120ns 每个Pulse |