admin管理员组文章数量:1567516
#线程详解
1. Thread基础之从 WinDbg 角度理解你必须知道的时间和空间上的开销
一:空间上的开销
1、thread本身来说就是操作系统的概念。。。
-
<1> thread的内核数据结构,其中有osid,context => CPU寄存器的里面的一些变量。 30 ms
-
<2>. thread 环境块 :
tls【thread本地存储】, execptionList 的信息。。。。
WinDbg 来给大家演示。。。 32,64 =可以达到clr的层面给大家展示底层知识
.loadby sos clr -
<3> 用户模式堆栈 内存溢出的一个异常 【堆栈溢出】
一个线程 分配 1M的堆栈空间,,【参数,局部变量】
-
<4> 内核模式堆栈
- 在CLR的线程操作,包括线程同步,大多都是调用底层的win32 函数 ,用户模式的参数需要传递到内核模式。。。
二、时间的开销
-
<1> 我们进程启动的时候,会加载很多的dll [托管和非托管的], exe,资源,元数据。。。。
-
进程启动的时候,我怎么没有看到应用程序域。。。
-
进程启动的时候,默认会有三个应用程序域。system domain, shared domain[int,long…] ,domain1.
-
开启一个thread,销毁一个thread 都会通知进程中的dll,attach,detach 标志位。。。
-
通知dll的目的就是 给thread做准备工作,比如销毁,让这些dll做资源清理。。。。
-
<2> 时间片切换
-
8个逻辑处理器,可供8个thread并行执行。。。。
-
比如说9个thread并发执行。 必然会有一个thread休眠30 ms。。。。
-
for => palleral for
2. Thread生命周期管理的Start,Suspend,Resume,Join,Interrupt,Abort五大方法介绍
一、在clr中Thread这个名字来表示线程这个概念
-
了解Thread的实例方法。。。 【id,ThreadState】
-
管理Thread生命周期 Start, Suspend, Resume, Intterupt,Abort。。。 Join 在使用Thread的时候是用的非 常多的。。。
1、Start演示
thread = new Thread(new ThreadStart(() =>
{
while (true)
{
try
{
Thread.Sleep(1000);
textBox1.Invoke(new Action(() =>{
textBox1.AppendText(string.Format("{0},", index++));
}));
}
catch (Exception ex)
{
MessageBox.Show(string.Format("{0}, {1}", ex.Message, index));
}
}
}));
thread.Start();
2、suspend演示
- 通过winddebug看一下thread 的状态。。。。
0:017> !ThreadState ab024
User Suspend Pending
Legal to Join
CLR Owns
CoInitialized
In Multi Threaded Apartment
Fully initialized
Sync Suspended
3、Resume演示
- 用来恢复suspend的暂停功能。。。
4、Interrupt 演示
-
用来中断处于WaitSleepJoin状态的线程。。。。
-
while(true){ continue… 效果}
-
当你调用interrupt的时候,会抛出一个interrupt的异常。。。。
5、Abort演示
- 通过抛出异常的方式销毁一个线程。。。。
- while(true) { break… 效果 }
- Interrupt 和 Abort做对比。。。
6、Join演示
- task.wait();
0:007> !ThreadState 202b020
Legal to Join
CLR Owns
CoInitialized
In Multi Threaded Apartment
Fully initialized
Interruptible
3、Thread静态方法之三大TLS操作 ThreadStatic、AllocateDataSlot、ThreadLocal
[线程本地存储]
- Thread中的一些静态方法 AllocateDataSlot、AllocateNamedDataSlot、GetNamedDataSlot、FreeNamedDataSlot,给所有线程分配一个数据槽。 存放数据。
- SetData
- GetData
一、变量 => Thread 的关系 t1, t2
-
《1》 t1 ,t2 共享 变量 public注意有“锁”的概念
-
《2》 t1 , t2 各自有一个 变量 internel 没有锁争用的概念
static void Main(string[] args)
{
var slot = Thread.AllocateNamedDataSlot("username");
//主线程 上 设置槽位,, 也就是hello world 只能被主线程读取,其他线程无法读取
Thread.SetData(slot, "hello world!!!");
var t = new Thread(() =>
{
var obj = Thread.GetData(slot);
Console.WriteLine("当前工作线程:{0}", obj);
});
t.Start();
var obj2 = Thread.GetData(slot);
Console.WriteLine("主线程:{0}", obj2);
Console.Read();
}
二、性能提升版: ThreadStatic
[ThreadStatic]
static string username = string.Empty;
static void Main(string[] args)
{
username = "hello world!!!";
var t = new Thread(() =>
{
Console.WriteLine("当前工作线程:{0}", username);
});
t.Start();
Console.WriteLine("主线程:{0}", username);
Console.Read();
}
三、ThreadLocal: 也是用来做 线程可见性
static void Main(string[] args)
{
ThreadLocal<string> local = new ThreadLocal<string>();
local.Value = "hello world!!!";
var t = new Thread(() =>
{
Console.WriteLine("当前工作线程:{0}", local.Value);
});
t.Start();
Console.WriteLine("主线程:{0}", local.Value);
Console.Read();
}
- 这些数据都是存放在线程环境块中。。【是线程的空间开销】 !teb来查看。。TLS: thread local storage。。。
4、Thread静态方法之经典 Release Bug 认识MemoryBarrier、VolatileRead、Write
【如何禁止编译器优化和Cache读取】
一、thread中一些静态方法 【内存栅栏】
-
MemoryBarrier、VolatileRead/Write 这些方法到底有什么用处。。。。
-
在实际项目中,我们都喜欢用Release版本,而不是Debug。。。。
-
因为Release中做了一些代码和缓存的优化。。。 比如说将一些数据从memory中读取到CPU高速缓存中。
二、release和debug到底性能差异有多大。。。
-
冒泡排序 O(N)2 1w * 1w = 1亿
-
从结果中可以看到,大概有5倍的差距。。。
-
在任何时候,不见得release都是好的。。有可能会给你引入一些bug。。。
class Program
{
static void Main(string[] args)
{
var path = Environment.CurrentDirectory + "//1.txt";
var list = System.IO.File.ReadAllLines(path).Select(i => Convert.ToInt32(i)).ToList();
for (int i = 0; i < 5; i++)
{
var watch = Stopwatch.StartNew();
var mylist = BubbleSort(list);
watch.Stop();
Console.WriteLine(watch.Elapsed);
}
Console.Read();
}
//冒泡排序算法
static List<int> BubbleSort(List<int> list)
{
int temp;
//第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次
for (int i = 0; i < list.Count - 1; i++)
{
//list.count-1:取数据最后一个数下标,
//j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。
for (int j = list.Count - 1; j > i; j--)
{
//如果前面一个数大于后面一个数则交换
if (list[j - 1] > list[j])
{
temp = list[j - 1];
list[j - 1] = list[j];
list[j] = temp;
}
}
}
return list;
}
}
}
static void Main(string[] args)
{
var isStop = false;
var t = new Thread(() =>
{
var isSuccess = false;
while (!isStop)
{
isSuccess = !isSuccess;
}
});
t.Start();
Thread.Sleep(1000);
isStop = true;
t.Join();
Console.WriteLine("主线程执行结束!");
Console.ReadLine();
}
-
上面这段代码在release环境下出现问题了。。。主线程不能执行结束。。。。
-
从代码中可以发现,有两个线程在共同一个isStop变量。。。
-
就是t这个线程会将isStop加载到Cpu Cache中。。。 【release大胆的优化】
-
两种方法解决:
- 1、不要让多个线程去操作 一个共享变量,否则容易出问题。
- 2、如果一定要这么做,那就需要使用节所涉及到的内容。
- MemoryBarrier VolatileRead/Write
- 不要进行缓存,每次读取数据都是从memrory中读取数据。。。
- MemoryBarrier => 在此方法之前的内存写入都要及时从cpu cache中更新到 memory。。。
- 在此方法之后的内存读取都要从memory中读取,而不是cpu cache。。。
static void Main(string[] args) { var isStop = 0; var t = new Thread(() => { var isSuccess = false; while (isStop == 0) { Thread.VolatileRead(ref isStop); isSuccess = !isSuccess; } }); t.Start(); Thread.Sleep(1000); isStop = 1; t.Join(); Console.WriteLine("主线程执行结束!"); Console.ReadLine(); }
5、从Windbg角度理解ThreadPool、Thread的差异、工作线程和IO线程的工作差异分析
一、thread 它是clr表示一个线程的数据结构
二、ThreadPool 线程池
-
thread 我如果想做一个异步任务,就需要开启一个Thread。 具有专有性。。。
-
ThreadPool =》 如果想做异步任务 只需要向租车公司借用 =》 使用完了就要归还
三、ThreadPool的使用方式
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
var func = obj as Func<string>;
Console.WriteLine("我是工作线程:{0}, content={1}", Thread.CurrentThread.ManagedThreadId,func());
}, new Func<string>(() => "hello world"));
Console.WriteLine("主线程ID:{0}", Thread.CurrentThread.ManagedThreadId);
Console.Read();
}
四、Thread 和 ThreadPool 到底多少区别。。。
-
现在有10个任务,如果用Thread来做,需要开启10个Thread
-
如果用ThreadPool来做,只需要将10个任务丢给线程池
-
windbg的角度来看一下两者的区别。。。。
-
1、区别: DeadThread: 10
- 虽然都挂掉了,但是没有被GC回收。。。。
- Thread( ) { this.InternalFinalize () ; }
- 从析构函数中可以看到 this.InternalFinalize(); 就是说销毁之后,先进入终结器。。。
- 《1》 或许能够被复活。。。 《2》 下次被GC回收。。。。
- 虽然thread已经死掉了,但是该占的资源还是要占。。。。
static void Main(string[] args)
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(() =>
{
for (int j = 0; j < 10; j++)
{
Console.WriteLine("work:{0},tid={1}", i, Thread.CurrentThread.ManagedThreadId);
}
});
thread.Name = "main" + i;
thread.Start();
}
Console.Read();
}
-
2.threadPool解决同样的问题。。。
-
从windbg中可以看到,当前没有死线程,而是都是默认初始化的。。。
DeadThread: 0
0:014> !threadpool
CPU utilization: 4%
Worker Thread: Total: 8 Running: 0 Idle: 8 MaxLimit: 2047 MinLimit: 8
Work Request in Queue: 0
Number of Timers: 0
Completion Port Thread:Total: 0 Free: 0 MaxFree: 16 CurrentLimit: 0 MaxLimit: 1000 MinLimit: 8
-
看到了当前的threadpool,
-
其中有“工作线程” 和 “IO线程”
-
工作线程: 给一般的异步任务执行的。。其中不涉及到 网络,文件 这些IO操作。。。 【开发者调用】
-
IO线程: 一般用在文件,网络IO上。。。 【CLR调用】
-
8的又来就是因为我有 8个逻辑处理器,也就是说可以8个Thread 并行处理。。。。
总结:
-
1.threadPool 可以用8个线程 解决 thread 10个线程干的事情,
-
节省了空间和时间:
-
时间: 通过各个托管和非托管的dll。。。
-
空间:teb,osthread结构, 堆栈。
6、定时任务之RegisterWaitForSingleObject、Timer以及专业的Quarz.Net的简介
一、定时器 Timer
- ThreadPool 也有定时器的功能。。。。
- 定时器的功能肯定需要 工作线程来处理。。。
1、ThreadPool 定时器功能
static void Main(string[] args)
{
ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(true), new WaitOrTimerCallback((obj, b) =>
{
//做逻辑判断,判断是否在否以时刻执行。。。
Console.WriteLine("obj={0},tid={1}, datetime={2}", obj, Thread.CurrentThread.ManagedThreadId,
DateTime.Now);
}), "hello world", 1000, false);
Console.Read();
}
-
一般在使用Timer的时候,有一个延期执行的功能。
-
windbg 来看一下底层线程是什么样的。。。。
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 3f54 01157bc8 2a020 Preemptive 02E8A3E4:00000000 01152258 1 MTA
5 2 2594 011678f8 2b220 Preemptive 00000000:00000000 01152258 0 MTA (Finalizer)
6 3 3c28 01189990 1020220 Preemptive 00000000:00000000 01152258 0 Ukn (Threadpool Worker)
7 4 121c 0118a2c0 8029220 Preemptive 02E8D8A4:00000000 01152258 0 MTA (Threadpool Completion Port)
8 5 28f4 0118bd70 8029220 Preemptive 00000000:00000000 01152258 0 MTA (Threadpool Completion Port)
0:009> !threadpool
CPU utilization: 9%
Worker Thread: Total: 0 Running: 0 Idle: 0 MaxLimit: 2047 MinLimit: 8
Work Request in Queue: 0
--------------------------------------
Number of Timers: 0
--------------------------------------
Completion Port Thread:Total: 2 Free: 2 MaxFree: 16 CurrentLimit: 2 MaxLimit: 1000 MinLimit: 8
二、Timer
-
System.threading 下面有timer
-
System.Timer 下面Timer。。。
-
System.Windows.Form 下面Timer。。。
-
System.Web.UI 下面Timer。。。
0:009> !threads
ThreadCount: 4
UnstartedThread: 0
BackgroundThread: 3
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 2d74 00f785c8 2a020 Preemptive 02E360F0:00000000 00f72030 1 MTA
5 2 3784 00f87ea0 2b220 Preemptive 00000000:00000000 00f72030 0 MTA (Finalizer)
6 3 2dc4 00faae18 102a220 Preemptive 00000000:00000000 00f72030 0 MTA (Threadpool Worker)
7 4 3e34 00fab748 1029220 Preemptive 02E3D4D0:00000000 00f72030 0 MTA (Threadpool Worker)
-
底层有一个队列 TimerQueue instance2 = TimerQueue.Instance; internal class TimerQueue
-
Timer 首先是用 ThreadPool.UnsafeQueueUserWorkItem(waitCallback, timer); 来完成定时功能。。
三、实战开发中,基本上不会用Timer来处理问题。。。。
-
因为处理的功能太少:
-
例:1、我希望早上8点执行。。。
-
2、我希望明天8点执行。。。
-
3、我希望每天8点执行。。。
-
4、我希望每个月的8号执行。。。
-
5、我希望下个月8号执行,排除双休日。。。
-
6、半个小时执行一次。。。
-
所以用第四种方法执行这些任务
↓
四、Quarz
Quartz.dll(详情请查文档)
7、Task和Task[T]之启动任务的三大方式Run,RunSynchronously,Start,StartNew以及内部代码异同点解析
一、Task 4.0
- 为什么要有Task。
- Task => Thread + ThreadPool + 优化和功能扩展
- Thread:容易造成时间 + 空间开销,而且使用不当,容易造成线程过多,导致时间片切换。。。
- ThreadPool:控制能力比较弱。 做thread的延续,阻塞,取消,超时等等功能。。。。
- 控制权在CLR,而不是在我们这里。。。
- Task 看起来像是一个Thread。。。
- Task 是在ThreadPool的基础上进行的封装。。。。
- 4.0之后,微软是极力的推荐 Task。。。来作为异步计算。。。
二:Task启动的几种方式
1、实例化的方式启动Task
Task task = new Task(() =>
{
Console.WriteLine("我是工作线程: tid={0}", Thread.CurrentThread.ManagedThreadId);
});
task.Start();
Console.Read();
2、TaskFactory的方式启动Task
//使用TaskFactory启动
var task = Task.Factory.StartNew(() =>
{
Console.WriteLine("我是工作线程: tid={0}", Thread.CurrentThread.ManagedThreadId);
});
3、Task.Run 方法
//使用Task的Run方法
var task = Task.Run(() =>
{
Console.WriteLine("我是工作线程: tid={0}", Thread.CurrentThread.ManagedThreadId);
});
4、Task的同步方法
//这个是同步执行。。。。也就是阻塞执行。。。
var task = new Task(() =>
{
Console.WriteLine("我是工作线程: tid={0}", Thread.CurrentThread.ManagedThreadId);
});
task.RunSynchronously();
三:Task是建立在ThreadPool上面吗???
-
我们的Task底层都是由不同的TaskScheduler支撑的。。。
-
TaskScheduler 相当于Task的CPU处理器。。。
-
默认的TaskScheduler是ThreadPoolTaskScheduler。。。
-
wpf中的TaskScheduler是 SynchronizationContextTaskScheduler
-
ThreadPoolTaskScheduler
-
this.m_taskScheduler.InternalQueueTask(this);
-
大家也可以自定义一些TaskScheduler。。。。
protected internal override void QueueTask(Task task)
{
if ((task.Options & TaskCreationOptions.LongRunning) != TaskCreationOptions.None)
{
new Thread(ThreadPoolTaskScheduler.s_longRunningThreadWork)
{
IsBackground = true
}.Start(task);
return;
}
bool forceGlobal = (task.Options & TaskCreationOptions.PreferFairness) > TaskCreationOptions.None;
ThreadPool.UnsafeQueueCustomWorkItem(task, forceGlobal);
}
四、Task
-
让Task具有返回值。。。 它的父类其实就是Task。。
-
具体的启动方式和Task是一样的。。。
8、Task详解之7种阻塞方式Wait,WaitAll,WaitAny,WhenAll,WhenAny,ContinueWith等实现任务的延续和阻塞代码解析
【这些都是task的核心】
一:task的阻塞和延续操作
1、阻塞 thread => Join方法 【让调用线程阻塞】
Thread t = new Thread(() =>
{
System.Threading.Thread.Sleep(100);
Console.WriteLine("我是工作线程1");
});
Thread t2 = new Thread(() =>
{
System.Threading.Thread.Sleep(100);
Console.WriteLine("我是工作线程2");
});
t.Start();
t2.Start();
t.Join(); // t1 && t2 都完成了 WaitAll操作。。。 WaitAny t1 || t2
t2.Join();
Console.WriteLine("我是主线程");
Console.Read();
2、延续
Task:
- WaitAll方法 必须其中所有的task执行完成才算完成
- WaitAny方法,只要其中一个task执行完成就算完成
- task.wait方法: 等待操作
- 上面这些等待操作,返回值都是void。。。。
- 现在有一个想法就是,我不想阻塞主线程实现一个waitall的操作。。。。
- t1 执行完成了执行 t2 ,这就是延续的概念。。。。
- 延续 = 它的基础就是wait。。。
static void Main(string[] args)
{
Task task1 = new Task(() =>{
System.Threading.Thread.Sleep(1000);
Console.WriteLine("我是工作线程1:{0}", DateTime.Now);
});
task1.Start();
Task task2 = new Task(() =>
{
System.Threading.Thread.Sleep(2000);
Console.WriteLine("我是工作线程2:{0}", DateTime.Now);
});
task2.Start();
Task.WhenAll(task1, task2).ContinueWith(t =>
{
//执行“工作线程3”的内容
Console.WriteLine("我是工作线程 {0}", DateTime.Now);
});
Console.Read();
}
-
WhenAll
-
WhenAny
-
Task工厂中的一些延续操作。。。
-
ContinueWhenAll
Task.Factory.ContinueWhenAll(new Task[2] {
task1, task2 }, (t) =>
{
//执行“工作线程3”的内容
Console.WriteLine("我是主线程 {0}", DateTime.Now);
});
-
ContinueWhenAny
-
介绍Task的7种阻塞方式 + 延续
-
如果会打组合拳,task异步任务还是写的非常漂亮。。。。
Program代码:例:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace ConsoleApplication2 { class Program { static void Main(string[] args) { Task task1 = new Task(() => { System.Threading.Thread.Sleep(1000); Console.WriteLine("我是工作线程1:{0}", DateTime.Now); }); task1.Start(); Task task2 = new Task(() => { System.Threading.Thread.Sleep(2000); Console.WriteLine("我是工作线程2:{0}", DateTime.Now); }); task2.Start(); //Task.WhenAny(task1, task2).ContinueWith(t => //{ // //执行“工作线程3”的内容 // Console.WriteLine("我是主线程 {0}", DateTime.Now); //}); //Task.WhenAll(task1, task2).ContinueWith(t => //{ // //执行“工作线程3”的内容 // Console.WriteLine("我是工作线程 {0}", DateTime.Now); //}); Console.Read(); } } }
9.Task实用枚举之TaskCreationOptions在【父子任务允许和拒绝,长任务,公平处理】分析
一、Task中的常见两种枚举
1、Task构造函数中使用的。。。
public Task(Action action, TaskCreationOptions creationOptions);
//
// 摘要:
// 指定可控制任务的创建和执行的可选行为的标志。
[Flags]
public enum TaskCreationOptions
{
//
// 摘要:
// 指定应使用默认行为。
None = 0,
//
// 摘要:
// 提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
PreferFairness = 1,
//
// 摘要:
// 指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。它会向 System.Threading.Tasks.TaskScheduler
// 提示,过度订阅可能是合理的。您可以通过过度订阅创建比可用硬件线程数更多的线程。
LongRunning = 2,
//
// 摘要:
// 指定将任务附加到任务层次结构中的某个父级。有关详细信息,请参阅 已附加和已分离的子任务。
AttachedToParent = 4,
//
// 摘要:
// 如果尝试附有子任务到创建的任务,指定 System.InvalidOperationException 将被引发。
DenyChildAttach = 8,
//
// 摘要:
// 防止环境计划程序被视为已创建任务的当前计划程序。这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default
// 当前计划程序。
HideScheduler = 16
}
2、任务延续中的枚举
public Task ContinueWith(Action continuationAction, TaskContinuationOptions continuationOptions);
二、演示
TaskCreationOptions :
- AttachedToParent :指定将任务附加到任务层次结构中的某个父级
- 建立了父子关系。。。 父任务想要继续执行,必须等待子任务执行完毕。。。。
- 看例子可以看到,其中是一个WaitAll的一个操作。。。
Task task = new Task(() =>
{
Task task1 = new Task(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}, TaskCreationOptions.AttachedToParent);
Task task2 = new Task(() =>
{
Thread.Sleep(10);
Console.WriteLine("task2");
}, TaskCreationOptions.AttachedToParent);
task1.Start();
task2.Start();
});
task.Start();
task.Wait(); //task.WaitAll(task1,task2);
Console.WriteLine("我是主线程!!!!");
Console.Read();
DenyChildAttach: 不让子任务附加到父任务上去。。。
static void Main(string[] args)
{
Task task = new Task(() =>
{
Task task1 = new Task(() =>
{
Thread.Sleep(100);
Console.WriteLine(
版权声明:本文标题:c#线程详解 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/xitong/1725605788a1032717.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论