目录
八、前台线程和后台线程
默认情况下,显式创建的线程是前台线程,通过手动的设置 Thread 类的属性 IsBackground = true 来指示当前线程为一个后台线程。
前台线程和后台线程最主要的区别是,所有的前台线程执行完成后,后台线程会自动停止工作。
注意事项:
不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。
托管线程池中的线程都是后台线程,使用 new Thread 方式创建的线程默认都是前台线程。
代码:
using System;
using System.Threading;
namespace Test5
{
internal class Program
{
static void Main(string[] args)
{
var sample1 = new ThreadSample(10);
var sample2 = new ThreadSample(20);
var thread1 = new Thread(sample1.CountNum);
thread1.Name = "thread1";
var thread2 = new Thread(sample1.CountNum);
thread2.Name = "thread2";
thread2.IsBackground = true;
thread1.Start();
thread2.Start();
Console.ReadKey();
}
}
class ThreadSample
{
private readonly int iterations;
public void CountNum()
{
for (int i = 0; i < iterations; i++)
{
Thread.Sleep(500);
Console.WriteLine("线程名:{0}, i:{1}", Thread.CurrentThread.Name, i);
}
}
public ThreadSample(int iterations)
{
this.iterations = iterations;
}
}
}
运行:
在上面的代码中可以看到定义如下
var sample1 = new ThreadSample(10);
var sample2 = new ThreadSample(20);
线程2的执行次数是20次,但是在打印中只执行了9次,线程2执行到第9次后,就停止运行了,因为这时候,前台线程已经执行完毕,后台线程也就跟随着一起结束了。
九、线程参数的传递
在线程的 Start 方法中,其实还有一个重载函数的,可以向线程执行的方法中传递一个参数,参考微软官方的代码:
[MethodImpl(MethodImplOptions.NoInlining)]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public void Start(object parameter)
{
if (m_Delegate is ThreadStart)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_ThreadWrongThreadStart"));
}
m_ThreadStartArg = parameter;
StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
Start(ref stackMark);
}
可以看到,参数的类型为 object 类型,下面就看看 start 方法中的这个参数的用途吧
using System;
using System.Threading;
namespace Test5
{
internal class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(Test);
thread.Start(10);
Console.ReadKey();
}
static void Test(object obj)
{
int len = (int)obj;
for (int i = 0; i < len; i++)
{
Console.WriteLine(i);
Thread.Sleep(500);
}
}
}
}
运行:
将上面的方法换为委托,Lambda 表达式的写法,效果一样
using System;
using System.Threading;
namespace Test5
{
internal class Program
{
static void Main(string[] args)
{
Thread thread = new Thread(delegate (object obj)
{
int len = (int)obj;
for (int i = 0; i < len; i++)
{
Console.WriteLine(i);
Thread.Sleep(500);
}
});
thread.Start(10);
Console.ReadKey();
}
}
}
十、线程中的 lock 关键字
lock 往往用在多线程的开发中,如果有几个线程,同时调用一个方法,就会出现这个方法轮流执行的情况,这可能会导致部分数据混乱,如果想这几个线程按顺序执行,则需要用到 lock。
下面是 lock 的一些注意点:
1.lock 只对多线程有效,对单线程无效,单线程 lock 不会导致死锁。
2.不推荐使用 lock(this),因为在它外部也可以访问它。
3.不应该使用 lock(string(类型)),因为 string 在内存分配上是重用的,可能会导致冲突。
4.lock 中包含的代码最好不要太多,因为在这里是单线程运行的。
5. .net 提供了一些线程安全的集合类,使用这些集合不需要用到 lock。
6.在可以使用数据分拆的方法来使用多线程时,最好使用数据分拆而不使用 lock。
7.lock 的对象应该是 private static readonly object Object_Lock = new object();
下面的代码,其实在上面的案例中用到了多次
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test5
{
internal class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(Test);
thread1.Name = "thread1";
thread1.Start();
Thread thread2 = new Thread(Test);
thread2.Name = "thread2";
thread2.Start();
Console.ReadKey();
}
static int count = 0;
static void Test()
{
while (count < 20)
{
count++;
Console.WriteLine("当前线程:{0},次数:{1}", Thread.CurrentThread.Name, count);
Thread.Sleep(50);
}
}
}
}
运行:
从结果可以看到,方法是由这两个线程轮流执行的,比较混乱,如果线程执行的方法中有全局变量,那么有可能导致计算结果是不对的,下面,我们将代码更改一下
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Test5
{
internal class Program
{
static void Main(string[] args)
{
Thread thread1 = new Thread(Test);
thread1.Name = "thread1";
thread1.Start();
Thread thread2 = new Thread(Test);
thread2.Name = "thread2";
thread2.Start();
Console.ReadKey();
}
private static readonly object obj = new object();
private static int thread1Count = 0;
private static int thread2Count = 0;
static void Test()
{
lock (obj)
{
while (true)
{
if (Thread.CurrentThread.Name == "thread1")
thread1Count++;
if (Thread.CurrentThread.Name == "thread2")
thread2Count++;
Console.WriteLine("线程:{0} thread1Count:{1}, thread2Count:{2}", Thread.CurrentThread.Name, thread1Count, thread2Count);
Thread.Sleep(50);
if (thread1Count > 5)
{
thread1Count = 0;
break;
}
if (thread2Count > 5)
{
thread2Count = 0;
break;
}
}
}
}
}
}
thread1Count = 0 是防止线程1执行完后,线程2执行时,依然满足条件,而直接退出了循环,所以,我将 thread1Count 设置为 0 了。
运行
从打印的结果来看,现在不再是两个线程轮流着执行了,这就是我们想要的效果了。
十一、Monitor类锁定
在上节中,我们使用了 lock 来锁定多线程的执行,实际上 lock 关键字是 Monitor 类用例的一个语法糖。如果我们分解使用了lock关键字的代码,将会看到它如下面代码所示:
object lockObject = new object();
bool acquiredLock = false;
try
{
Monitor.Enter(lockObject, ref acquiredLock);
}
finally
{
if (acquiredLock)
{
Monitor.Exit(lockObject);
}
}
Monitor的常用属性和方法:
- Enter(Object) 在指定对象上获取排他锁。
- Exit(Object) 释放指定对象上的排他锁。
- Pulse 通知等待队列中的线程锁定对象状态的更改。
- PulseAll 通知所有的等待线程对象状态的更改。
- TryEnter(Object) 试图获取指定对象的排他锁。
- TryEnter(Object, Boolean) 尝试获取指定对象上的排他锁,并自动设置一个值,指示是否得到了该锁。
- Wait(Object) 释放对象上的锁并阻止当前线程,直到它重新获取该锁。
案例:
using System;
using System.Threading;
namespace Test5
{
internal class Program
{
static void Main(string[] args)
{
Thread threadA = new Thread(ThreadMethod);
threadA.Name = "线程A";
Thread threadB = new Thread(ThreadMethod);
threadB.Name = "线程B";
threadA.Start();
threadB.Start();
Console.ReadKey();
}
private static readonly object obj = new object();
public static void ThreadMethod()
{
//锁定对象
Monitor.Enter(obj);
try
{
for (int i = 1; i <= 10; i++)
{
Console.Write("{0}:{1} ", Thread.CurrentThread.Name, i);
}
Console.WriteLine();
}
finally
{
//释放对象
Monitor.Exit(obj);
}
}
}
}
运行:
结束
如果这个帖子对你有所帮助,欢迎 关注 + 点赞 + 留言,谢谢!
end