- “找不到类型{..} 它在 ServiceHost 指令中提供为 Service 特性值,或在配置元素 system.serviceModel/serviceHostingEnvironment/serviceActivations 中提供。”
- 确认.svc文件中的<%@ ServiceHost Language="C#" Debug="true" Service="{ServiceName}" CodeBehind="....cs" %>的{ServiceName}是不是跟Web.config中的服务名称一致。
- “自定义工具警告: 没有找到与Silverlight 4 兼容的端点。生成的客户端类将不可用,除非通过构造函数提供端点信息。”
- WCF是否启用了Silverlight支持?WCF有WCF服务,WCF数据服务,在Silverlight中还有启用Silverlight支持的WCF,靠。
- [配置服务引用...],取消选择[重新使用引用的程序集中的类型]。
- 发布到Sharepoint中的Silverlight调用外域的WCF服务
- 将添加WCF服务时生成的Reference.cs从Service References移动到项目文件夹下,可以重命名。
- 修改...Client代码:
EndpointAddress endPointAddress = new EndpointAddress("{wcf uri}"); CustomBinding binding = new CustomBinding(); binding.Elements.Add(new BinaryMessageEncodingBindingElement()); binding.Elements.Add(new HttpTransportBindingElement()); Adomd.AnalysisService.AdomdConnectorClient client = new Adomd.AnalysisService.AdomdConnectorClient(binding, endPointAddress);注意:如果不使用CutomBinding而用BasicHttpBinding就会返回:Not Found错误,主要是协议的问题,这是跟踪HTTP请求时发现的。
- 在WCF宿主目录下(跟web.config一起)添加跨域策略文件clientaccesspolicy.xml:
-
<?xml version="1.0" encoding="utf-8" ?> <access-policy> <cross-domain-access> <policy> <allow-from http-request-headers="*"> <domain uri="*"/> </allow-from> <grant-to> <resource path="/" include-subpaths="true"/> </grant-to> </policy> </cross-domain-access> </access-policy>
- 如何打开IncludeExceptionDetailInFaults,修改web.config(如果是Sharepoint,那么是在\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\WebServices\SecurityToken\):
<configuration><behaviors><serviceBehaviors><behavior name="">... <serviceDebug includeExceptionDetailInFaults="true" />
...
- 当发布到Sharepoint中的Silverlight部件调用外部WCF服务时遇到CrossDomainError怎么办,而且已经采用了clientaccesspolicy?
既然发布了,就别在Uri中使用localhost啦 - 收藏:http://www.nanmu.net/sharepoint2010/sharepoint-2010-chinese/default.aspx
- [吐血]Sharepoint中使用FBA验证,无法选择人员!
- 首先学习http://msdn.microsoft.com/zh-cn/library/bb975136(v=office.12).aspx,正确配置Sharepoint
- 无法选择人员的原因,使用ULSViewer看log吧
- 提供程序名称不对
- 提供的凭证无法访问FBA DB,就这么个问题,折腾我半天啊
- Security Token Service 不可用,无法激活服务“/SecurityTokenServiceApplication/securitytoken.svc ?
- 一种可能性是因为SecurityTokenServiceApplicationPool的启用32位应用程序=True,需要改成False;
- 不过,还有可能就是瞎改SecurityTokenServiceApplication的web.config了,如何重新启用呢?
- 参考:http://blogs.msdn.com/b/sowmyancs/archive/2010/07/16/sharepoint-2010-service-applications-bcs-metadata-access-service-are-not-working.aspx
- 先使用Sharepoint 2010产品配置向导,可能会有错误导致失败,不过没关系,只是整理下思路;
-
$sts = Get-SPServiceApplication | ?{$_ -match "Security"}$sts.Provision()
-
Exception of type 'System.ArgumentException' was thrown. Parameter name: encodedValue ...蛋疼了,在Sharepoint管理中点什么都错误?
原因:
“
This error occurs when your site is configured for claims and you are logged into an account thats isn't being formatted as a proper claim.
E.g.,
If your site is claims and you log in VIA Ad and the Welcome control says "Domain\SomeUser" that is not a properly encoded claim, so when the OOTB Claim Provider try's to decode the claim, it errors out with encodedValue.
The code that processes that encoded value is looking for an "i" before a "|" in the login name "Domain\UserName" and there isn't one, so it throws an error that the passed in encodedValue is invalid or null etc.
This usually indicates that your web config files are not properly configured for a site running in claims, or you could have FBA set up and that's not configured properly either.
Refer to this link for that,
http://blogs.technet.com/b/speschka/archive/2009/11/05/configuring-forms-based-authentication-in-sharepoint-2010.aspx
In some cases, if your using extremely custom authentication logic "like a custom Trusted Identity Provider" then you need to write your own ClaimProvider so sharepoint knows how to encoded/decode the claims.
When it's result and you log in Via a Windows account, your login name should look something like "i#:w|Domain\UserName" (*spelling*). And when logged in via fba it should look like "i#:membershipprovidernamehere|usernamehere" (*spelling*)
This error can also occur if you have your SuperReader and Cache Reader accounts configured with improper claim values. When using a Claims Configuration, your SuperReader and CacheReader account names need to be Encoded Claims like the examples above. And I bet this is what's happening in your example,
When you try to get an SPSite, because you have the Cache Accounts configured, it's trying to check the cache to see if the Site exists in the Cache, but when it does it dies when trying to evaluate the SuperReader and SuperUser accounts because it can't decode their claims because their not valid encoded claims, then you get the encodedValue exception. It works sometimes because the object might not be cached, so it gets a new one, then when the object is cached it dies.
Or it might work when your logged in via AD but die when logged in via FBA etc (just a hypothetical theory).
Heres an article on this issue as well. (I struggled with this for a good week before I found this).
http://technet.microsoft.com/en-us/library/ff758656.aspx
My Blog: http://www.thesug.org/Blogs/ryan_mann1/default.aspx Website: Under Construction
”
我把配置FBA时所改的3个web.config都改回去了,当时偷懒了直接使用继承的AspNetSqlRoleProvider,这样是不行。
- “An exception occurred when trying to issue security token: The security token username and password could not be validated.”或者“无法验证安全令牌用户名和密码”
- 有一种可能:该用户被锁定了,先解锁该用户。还不行的话,就有点麻烦了,继续往下;
- FBA的SQL数据库中早先建的用户是不能用的,即使在网站集管理中已经设子,正确步骤:
- 正确配置完三个web.config之后,在IIS中对应的Web站点中,设置默认的角色提供者和用户提供者为对应的FBA提供者;
- 创建角色和用户;
- 将默认的角色提供者和用户提供者改回去;
- 参考:http://msdn.microsoft.com/zh-cn/library/gg252020.aspx#ClaimsExample3_Step2
-
declare @now datetime set @now= GETDATE() exec aspnet_Membership_CreateUser 'MyAppName','bob','pass@word1', '','bob@contoso.com','','',1,@now,@now,0,0,null exec aspnet_Membership_CreateUser 'MyAppName','mary','pass@word1', '','mary@contoso.com','','',1,@now,@now,0,0,null exec aspnet_Membership_CreateUser 'MyAppName','jack','pass@word1', '','jack@contoso.com','','',1,@now,@now,0,0,null EXEC aspnet_Roles_CreateRole 'MyAppName', 'Employee' EXEC aspnet_Roles_CreateRole 'MyAppName', 'TeamManager' EXEC aspnet_Roles_CreateRole 'MyAppName', 'CEO' EXEC aspnet_UsersInRoles_AddUsersToRoles 'MyAppName', 'bob', 'Employee', 8 EXEC aspnet_UsersInRoles_AddUsersToRoles 'MyAppName', 'mary', 'TeamManager', 8 EXEC aspnet_UsersInRoles_AddUsersToRoles 'MyAppName', 'jack', 'CEO', 8 EXEC aspnet_UsersInRoles_AddUsersToRoles 'MyAppName', 'jack', 'Admin', 8
- Sharepoint Designer 无法打开站点,“服务器不能完成你的请求。有关详细信息请单击‘详细信息按钮”
- 详细信息啥都没有,ULSView也啥都没有,纠结人呢
- Google的结果是http://social.technet.microsoft.com/Forums/en-US/sharepoint2010customization/thread/19628c53-fde2-44b0-806c-64f67263c931/ 。“Sam, there is an issue with web applications that have more than one binding in IIS. When there is more than one, the site can't be opened. To get around this, I have extended my web application to a new URL (host header) and used that URL to connect with SPD2010.”,大致意思是IIS中多个主机头绑定,可我没有啊;
- 好不容易在系统日志中发现点蛛丝马迹:"
WebHost 无法处理请求。
"
发件人信息: System.ServiceModel.ServiceHostingEnvironment+HostingManager/39147728
异常: System.ServiceModel.ServiceActivationException: 由于编译过程中出现异常,无法激活服务“/_vti_bin/client.svc”。异常消息为: 无法创建相对 URI,因为“uriString”参数表示绝对 URI。http://xxx:2222/_vti_bin/client.svc。 ---> System.UriFormatException: 无法创建相对 URI,因为“uriString”参数表示绝对 URI。http://xxx:2222/_vti_bin/client.svc
在 System.ServiceModel.Activation.ApplyHostConfigurationBehavior.ThrowIfAbsolute(Uri uri)
在 System.ServiceModel.Activation.ApplyHostConfigurationBehavior.FailActivationIfEndpointsHaveAbsoluteAddress(ServiceHostBase service)
在 System.ServiceModel.Description.DispatcherBuilder.ValidateDescription(ServiceDescription description, ServiceHostBase serviceHost)
在 System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
在 System.ServiceModel.ServiceHostBase.InitializeRuntime()
在 System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
在 System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
在 System.ServiceModel.ServiceHostingEnvironment.HostingManager.ActivateService(String normalizedVirtualPath)
在 System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath)
--- 内部异常堆栈跟踪的结尾 ---
在 System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath)
在 System.ServiceModel.ServiceHostingEnvironment.EnsureServiceAvailableFast(String relativeVirtualPath)
进程名称: w3wp
进程 ID: 7828 - Client.svc确实没有激活,而管理网站中是可以的。看来问题还是在主机头绑定上
- 再次检查web.config,发现<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />,手贱呢,一个晚上就因为这么这原因,删掉就好,嘛的,Sharepoint Designer终于可以连上了。
- 挖坑ing...
A Simple Hybrid Lock
- 混合结构在没有竞争的情况下,提供了原生用户模式同步结构的性能优点。同时在有竞争的情况下,提供了核心模式同步结构不浪费CPU资源的优点;
- SimpleHybridLock:
internal sealed class SimpleHybridLock : IDisposable { // The Int32 is used by the primitive user-mode constructs (Interlocked methods) private Int32 m_waiters = 0; // The AutoResetEvent is the primitive kernel-mode construct private AutoResetEvent m_waiterLock = new AutoResetEvent(false); public void Enter() { // Indicate that this thread wants the lock if (Interlocked.Increment(ref m_waiters) == 1) return; // Lock was free, no contention, just return // Another thread is waiting. There is contention, block this thread m_waiterLock.WaitOne(); // Bad performance hit here // When WaitOne returns, this thread now has the lock } public void Leave() { // This thread is releasing the lock if (Interlocked.Decrement(ref m_waiters) == 0) return; // No other threads are blocked, just return // Other threads are blocked, wake 1 of them m_waiterLock.Set(); // Bad performance hit here. } }
Spinning, Thread Ownership, and Recursion
- AnotherHybridLock:
internal sealed class AnotherHybridLock : IDisposable { // The Int32 is used by the primitive user-mode constructs (Interlocked methods) private Int32 m_waiters = 0; // The AutoResetEvent is the primitive kernel-mode construct private AutoResetEvent m_waiterLock = new AutoResetEvent(false); // This field controls spinning in an effort to improve performance private Int32 m_spinCount = 4000; // Arbitrarily chosen count // These fields indicate which thread owns the lock and how many times it owns it private Int32 m_owningThreadId = 0, m_recursion = 0; public void Enter() { // If calling thread already owns the lock, increment recursion count and return Int32 threadId = Thread.CurrentThread.ManagedThreadId; if (threadId == m_owningThreadId) { m_recursion++; return; } // The calling thread doesn't own the lock, try to get it SpinWait spinwait = new SpinWait(); for (Int32 spinCount = 0; spinCount < m_spinCount; spinCount++) { // If the lock was free, this thread got it; set some state and return if (Interlocked.CompareExchange(ref m_waiters, 1, 0) == 0) goto GotLock; // Black magic: give other threads a chance to run // in hopes that the lock will be released spinWait.SpinOnce(); } // Spinning is over and the lock was still not obtained, try one more time if (Interlocked.Increment(ref m_waiters) > 1) { // Other threads are blocked and this thread must block too m_waiterLock.WaitOne(); // Wait for the lock; preformance hit // When this thread wakes, it owns the lock; set some state and return } GotLock: // When a thread gets the lock, we record its ID and // indicate that the thread owns the lock once m_owningThreadId = threadId; m_recursion = 1; } public void Leave() { // If the calling thread doesn't own the lock, there is a bug Int32 threadId = Thread.CurrentThread.ManagedThreadId; if (threadId != m_owningThreadId) throw new SynchronizationLockException("Lock not owned by calling thread"); // Decrement the recursion count. If this thread still owns the lock, just return if (--m_recursion > 0) return; // If no other threads are blocked, just return if (Interlocked.Decrement(ref m_waiters) == 0) return; // Other threads are bloced, wake 1 of them m_waiterLock.Set(); // Bad performance hit here } } - 性能比较参考:
Incrementing x: 8 Fastest Incrementing x in Mutex: 50 6x slower Incrementing x in SimpleSpinLock: 210 26x slower Incrementing x in SimpleHybridLock: 211 26x slower (similar to SimpleSpinLock) Incrementing x in AnotherHybridLock: 415 52x slower (due to ownership/recursion) Incrementing x in SimpleWaitLock: 17,615 2,201x slower
A Potpourri of Hybrid Constructs
- System.Threading.ManualResetEventSlim,System.Threading.SemaphoreSlim:跟对应的ManualResetEvent, Semaphore很相似,不同点:直到第一次竞争出现,它们将轮转在用户模式并推迟创建内核模式同步结构;另外,Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken)方法可以传入超时参数和CancellationToken;
- Monitor(关键字lock,在JIT时会使用Monitor),一个常用的混合模式同步结构类,提供了支持轮转、线程所有权、递归计数的互斥锁;
- 每个托管堆上的对象都有一个叫做“同步块(Sync Block)”的数据结构跟它相关联,该数据结构具备字段:内核对象、所属线程ID、递归计数、等待线程数量。
- 当CLR初始化时,会分配一个同步块数组。每个托管堆上的对象,都有两个开销字段:类型对象指针(Type Object Pointer)、同步块索引(Sync Block Index) -- 关联到系统同步块数组的索引(-1:不关联任何同步块);
- 当Mointer.Enter方法被调用后,CLR会在数组中找到一个空闲的同步块,然后设置对象的同步块索引指向同步块。当Monitor.Exit被调用后,CLR会检查是否有其他线程在等待使用对象的同步块。如果没有,同步块被释放,对象的Sync Block Index被设置成-1;
- 同步块可以被关联到类型对象,类型对象的引用可以传递给Monitor的方法;
- Monitor.Enter(this), .Exit(this),这里会出现一个比较晦涩的BUG,this所代表的对象在外部可能会被作为锁,这样就不能正常工作,所以总是使用一个私有锁代替,
private readonly Object m_lock = new Object(); ... Monitor.Enter(m_lock); ... Monitor.Exit(m_lock);; - Monitor是一个静态类,所以存在一些问题需要注意:
- 如果对象的类型指向System.MarshalByRefObject类型的子类,一个变量可以引用到一个代理对象。当通过传递代理对象的引用调用Monitor的方法时,锁住的是代理对象,而不是实际对象;
- 如果一个线程调用Monitor.Enter时,传递一个以及域中立加载的类型对象的引用,线程给进程中所有AppDomain中的该类型上锁。这是一个BUG,所以绝不使用类型对象的引用调用Monitor的方法;
- 因为Monitor的方法使用Object做参数,所以传递一个值类型会引起装箱操作。这样锁住的是装箱之后的对象,所以一切同步都白搭;
- 使用[MethodImpl(MethodImplOptions.Synchronized)]属性到一个方法上,会引起JIT编译器使用Monitor.Enter和Monitor.Exit包转在本地代码上,如果该方法是个实例方法,那么this就是Monitor方法的参数,锁住的是隐式的公共锁(可能是个噩梦)。如果该方法是静态方法,那么类型的类型对象就是Monitor方法的参数,那就噩梦连绵了。所以,绝不要使用在方法上使用[MethodImpl(MethodImplOptions.Synchronized)]属性
- 当调用类型的类型构造器(静态构造器),CLR在类型的类型对象上加锁以确定只有一个线程初始化该类型对象和它的静态字段。建议:尽可能的避免类型构造器,或者保证它短小精干。
- lock会引入Monitor和try/finally块,而JIT不会内联编译带有try块的方法,这会导致降低性能。所以一般不推荐使用lock关键字;
- ReaderWriterLockSlim, OneManyLock, CountdownEvent, Barrier, ...
- 建议写代码时,别阻塞所有的线程,尽可能短的持有锁;
- 读写锁通常比Monitor要慢,但是允许多个读线程并发执行,改善了整体性能,同时最小化了线程阻塞的可能;
- 避免使用递归锁(特别是读写递归锁),因为太糟蹋性能了;
- 避免在finally中释放锁,因为进入和离开异常处理块会有性能负担。尤其是在改变状态时出现了异常,那么会导致无法预知的行为,并出现安全漏洞;
- 面向计算方向的工作,还是使用task;
- 面向I/O方向的工作,采用APM在I/O操作完成后调用回调方法;
- 可以使用SpinLock代替Monitor因为SpinLock稍微快一点,但是SpinLock可能会浪费CPU。Monitor实际上也挺快,因为使用本地代码实现的,而不是托管代码;
The Famous Double-Check Locking Technique
- 延迟初始化,单例对象知道应用程序使用它才开始初始化,节约时间和内存。潜在问题会出现在当多个线程同时首次访问该对象时;
- The "Double-Checked Locking is Broken" Declaration
- 不幸的Java两次检查锁单例代码,如果编译器进行优化或者共享内存的多处理器环境下,下面的代码不会正常工作,原因有一大箩筐。
// Broken multithreaded version // "Double-Checked Locking" idiom class Foo { private Helper helper = null; public Helper getHelper() { if (helper == null) synchronized(this) { if (helper == null) helper = new Helper(); } return helper; } // other functions and members... }简单的办法,使用静态字段:class HelperSingleton { static Helper singleton = new Helper(); }或者,使用JDK5的新语法,volatile关键字:// Works with acquire/release semantics for volatile // Broken under current semantics for volatile class Foo { private volatile Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (helper == null) helper = new Helper(); } } return helper; } } - CLR支持Double-Checked Locking,C#版:
internal sealed class Singleton { private static readonly Object s_lock = new Object(); private static Singleton s_value = null; private Singleton() { } public static Singleton GetSingleton() { if (s_value != null) return s_value; Monitor.Enter(s_lock); if (s_value == null) { Singleton temp = new Singleton(); Interlocked.Exchange(ref s_value, temp); } Monitor.Exit(s_lock); return s_value; } } - 其实,这才是最实用的单例模式:
internal sealed class Singleton { private static Singleton s_value = new Singleton(); private Singleton() { } public static Singleton GetSingleton() { return s_value; } }因为CLR在代码第一次尝试访问类的成员时,会自动调用类型的类构造器(class initializer, type initializer, static initializer)。线程第一次调用Singleton.GetSingletonk静态方法时,CLR自动调用类构造器(注意Before-Field-Init语义),创建出一个该对象的实例。并且:CLR保证调用类型构造器是线程安全的。 - 上面的代码有一点点遗憾,如果Singleton中有其他静态成员,如果任何一个被访问时,Singleton对象就会被创建,这可能并不是所想要的。那么下面的代码简单实用高效可靠:
internal sealed class Singleton { private static Singleton s_value = null; private Singleton() { } public static Singleton GetSingleton() { if (s_value != null) return s_value; Singleton temp = new Singleton(); Interlocked.CompareExchange(ref s_value, temp, null); return s_value; } } - System.Lazy
,System.Threading.LazyInitializer的静态方法,延迟加载和延迟初始化。
The Condition Variable Pattern
- 当满足一组复杂的条件时,线程执行特定的代码,否则不停轮转检测条件,这通常会浪费CPU时间,条件变量模式(condition variable pattern)可以基于复杂条件有效的进行同步操作;
- 简单的条件变量模式例子:
internal sealed class ConditionVariablePattern { private readonly Object m_lock = new Object(); private Boolean m_condition = false; public void Thread1() { Monitor.Enter(m_lock); while (!m_condition) { Monitor.Wait(m_lock); } Monitor.Exit(m_lock); } public void Thread2() { Monitor.Enter(m_lock); m_condition = true; Monitor.PulseAll(m_lock); Monitor.Exit(m_lock); } } - 线程安全的队列:
internal sealed class SynchronizedQueue<T> { private readonly Object m_lock = new Object(); private readonly Queue<T> m_queue = new Queue<T>(); public void Enqueue(T item) { Monitor.Enter(m_lock); m_queue.Enqueue(item); // After enqueuing an item, wake up any/all waiters; Monitor.PulseAll(m_lock); Monitor.Exit(m_lock); } public T Dequeue() { Monitor.Enter(m_lock); while (m_queue.Count == 0) Monitor.Wait(lock); T item = m_queue.Dequeue(); Monitor.Exit(m_lock); return item; } }
Using Collections to Aviod Holding a Lock for a Long Time
- System.Threading.Tasks所提供的往往更适合,为什么呢,比如Task:
- 比线程使用更少的内存,创建和销毁的时间更短;
- 线程池会根据CPU的情况自动扩容或收缩;
- Task完成一个阶段,执行Task的线程回到线程池,然后线程可以干点其他的事;
- 线程池是全局进程可见的,可以更好的调度任务,减少进程中的线程数,同时减少线程上下文切换的开销。
- Reader-Writer锁非常有用。Power Threading库有个非阻塞的读写类ReaderWriterGate。
The Concurrent Collection Classes
- FCL提供了4个线程安全的集合类型:System.Collections.Concurrent's (ConcurrentQueue, ConcurrentStack, ConcurrentDictionary)(MSCorLib.DLL), ConcurrentBag(System.DLL);
- ConcurrentDictionary内部使用Monitor;
- ConcurrentQueue, ConcurrentStack没有锁,内部使用Interlocked方法来维护集合;
- ConcurrentBag内部由每个线程一个迷你集合容器组成;
- ConcurrentStack, ConcurrentQueue, ConcurrentBag, .GetEnumerator方法返回集合内容的快照;
- ConcurrentDictionary.GetEnumerator方法不是返回内容的快照,要注意在枚举的过程中元素状态会发生改变。
本章小结
本章讲述的是混合的线程同步模式,首先通过一个简单的例子演示了如何混合使用用户模式和核心模式的同步结构。然后说明了轮转、线程所有制、锁递归的概念。接着列举了几种混合同步结构的实例,并进行了分析比较。本章还讨论了一个非常有意思的问题:单例模式的两次检查加锁情况,给出了正确实现单例模式的方法。然后讲了什么是条件变量模式,以及如何通过使用集合、Task和线程池来避免长时间持有锁。最后简单说明了四个并发集合类。
Class Libraries and Thread Safety
- 线程同步是用来避免多个线程同时访问共享数据时出现冲突;
- 线程同步的障碍:
- 1.极其乏味易错;
- 2.锁严重影响性能;
- 3.线程同步锁在同一时间点仅允许一个线程访问资源。
- 设计程序时应该尽可能的避免线程同步,最好避免采用static字段的共享数据;
- 尝试使用值类型,因为它们总是拷贝传递的,因此每个线程在自身拥有的拷贝上操作。所以当多个线程同时以只读方式访问值类型共享数据时是安全的。
- FCL保证所有的静态方法是线程安全的;
- 当一个线程构造一个对象,只有该线程拥有该对象的引用,其他线程不能访问该对象;
- 类型设计时应遵循的模式:确保所有的静态方法多线程安全、所有的实例方法多线程不安全;
一个例外:如果实例方法是用于协调多线程的,那么该实例方法也应该多线程安全。如CancellationTokenSource.Cancel方法。
Primitive User-Mode and Kernel-Mode Constructs
- 两种原生线程同步结构:
- 用户模式:速度很快,使用特定的CPU指令协调线程,协调工作由硬件完成。Windows系统不会检测线程是否阻塞在用户模式同步结构;
线程池线程阻塞在用户模式同步结构不会被当成阻塞,线程池不会创建新的线程来代替临时阻塞的线程。
采用该模式的线程会被系统抢占调度,可能导致线程被反复快速调度,从而会浪费CPU; - 核心模式:Windows操作系统提供,调用实现在系统内核的函数。当一个线程使用内核模式同步结构来请求其他线程持有的资源,Windows将阻塞该线程所以不会浪费CPU;
线程在用户模式与核心模式间互相转换会严重损害性能; - 如果一个线程持有一个同步结构不再释放它,等待该结构的线程将永远被阻塞:
- 活锁:如果该同步结构是用户模式,线程一直运行在CPU上;
- 死锁:如果该同步结构是核心模式,线程被阻塞住;
- 活锁与死锁都很糟糕,但相比之下,活锁更糟糕,因为活锁既浪费CPU又浪费内存,而死锁只浪费内存;
- 用户模式:速度很快,使用特定的CPU指令协调线程,协调工作由硬件完成。Windows系统不会检测线程是否阻塞在用户模式同步结构;
User-Mode Constructs
- 原生的用户模式结构:
- 易变(volatile)结构:在一个简单数据类型变量上执行原子读或写操作;
- 联锁(Interlocked)结构:在一个简单数据类型变量上执行原子读和写操作;
- 易变结构确认读或写操作是否原子的非常重要,它们还控制这些原子操作的时机。联锁结构执行操作要比简单的读和写操作复杂一些,它们也需要控制操作的时机;
- 比如一个Int64的变量如果没有正确对齐,那么当多线程读写该变量时,可能会出现只正确读取前四字节或后四字节,这就是脏读(torn read)?
- System.Threading.Thread.VolatileWrite(), .VolatileRead(), .MemoryBarrier()静态方法通常禁止C#编译器、JIT编译器、CPU进行优化;
- 当线程通过共享数据进行通信时,对最后一个值的写操作调用VolatileWrite(),对第一个值的读操作调用VolatileRead();
- C# volatile关键字:JIT编译器会对标记为volatile的字段的读写进行VolatileWrite()和VolatileRead()处理;
- 将来,C#不支持通过引用传递volatile字段到方法调用中;
- 现在,Interlocked.Exchange(), .CompareExchange()只支持简单值类型,将来还会提供对Object, IntPtr, Single, Double以及泛型版本的支持;
- SimpleSpinLock的缺陷,当锁出现竞争时,线程会不停轮转,浪费CPU资源:
internal struct SimpleSpinLock { private Int32 m_ResourceInUse; //0 = false (default), 1 = true public void Enter() { // Set the resource to in-use and if this thread // changed it from Free, then return while (Interlocked.Exchange(ref m_ResourceInUse, 1) != 0) { // Black Magic goes here... } } public void Leave() { // Mark the resource as Free Thread.VolatileWrite(ref m_ResourceInUse, 0); } } - FCL提供的System.Threading.SpinWait;
- Thread.Sleep(),允许线程资源放弃时间片,休眠时间是近似值;
- Thread.Yield(),告诉Windows在当前CPU上调度其他线程。如果当前CPU上有其他线程,返回true并结束调用线程的时间片,被调度线程结束自身的时间片之后,调用线程继续。
- Thead.SpinWait(),线程强制自己暂停,允许超线程CPU切换到其他线程。该方法会使用特别的CPU指令,如果CPU不支持超线程,该指令被忽略;
- Win32 API:Sleep(), SwitchToThread(), YieldProcessor();
- 当集合中的每一项都需要关联锁的时候,轻量级、内存友好的SpinLock是个好东西。但是要注意不要传递它的实例,因为是值类型,传递的是拷贝;
Kernel-Mode Constructs
- 核心模式的同步结构比用户模式的要慢许多,因为它们由Windows操作系统协调,此外每个核心对象上的方法调用都会导致调用线程从托管代码转换到本地用户模式代码再到本地核心模式代码,然后再原路返回;
- 核心模式同步结构提供哪些用户模式所没有的优点:
- 当核心模式同步结构检测到资源上出现竞争时,Windows阻塞竞争失败的线程,因此不再浪费处理器资源;
- 核心模式同步结构可以同步本地线程和托管线程;
- 核心模式同步结构可以同步运行在同一台机器上不同处理器上的线程;
- 核心模式同步结构可以附上安全限制避免未经认证的帐号访问他们;
- 线程能够被阻塞直到所有的核心模式同步结构都可用,或者任何一个可用;
- 阻塞在核心模式同步结构上的线程可以设置一个timeout值;如果这段时间内没能获得期望的资源,那么不再阻塞以干点其他正事。
- 原生的核心模式结构:
- 事件(Events):事件是有内核管理的Boolean变量。当事件为false时,线程被阻塞。有两种事件:AutoResetEvent、ManualResetEvent;
- 信号量(Semaphore):信号量是内核管理的Int32变量。当信号量为0时,线程被阻塞;>0时,线程不被阻塞。
- 核心模式结构的层次结构:
WaitHandle |--EventWaitHandle |--|--AutoResetEvent |--|--ManualResetEvent |--Semaphore |--Mutex
- 每个在核心模式同步结构上的方法调用都会出现完全内存保护;
- SimpleWaitLock vs SimpleSpinLock:
当锁上没有竞争时,SimpleWaitLock比SimpleSpinLock要慢许多,因为SimpleWaitLock的Enter和Leave方法会强制调用线程在托管代码到核心代码之间进行来回转换;
但是当锁上有竞争时,SimpleWaitLock不会浪费CPU资源,而SimpleSpinLock会不停的轮转CPU; - AutoResetEvent、ManualResetEvent、Semaphore行为比较:
- 当多个线程在AutoResetEvent上等待时,设置事件只会让一个线程不再被阻塞;
- 当多个线程在ManualResetEvent上等待时,设置事件会让所有线程不再被阻塞;
- 当多个线程在Semaphore上等待时,释放信号量会让releaseCount个线程不再被阻塞(relaseCount是Semaphore.Release()方法的参数)。
- 互斥体(Mutex)表现为一个同斥锁;
- Mutex很像AutoResetEvent或者Semaphore(count = 1),但有一些更复杂的逻辑:
- 首先,Mutex对象通过查询调用线程的ID记录了哪个线程获得了它。当线程调用ReleaseMutex,Mutex会确认是否为同一线程;
- 其次,Mutex维护一个递归计数指出被获得了多少次。只有当递归计数降为0时,其他线程才能获取。
- Mutex对象需要更多的内存,并且需要维护额外信息,所以会比较慢;
- 如何当一个单独的核心结构可用时调用方法?使用System.Threading.ThreadPool.RegisterWaitForSingleObject静态方法。
本章小结
本章讲述了原生的线程同步结构,首先介绍了类库和线程安全性概念,然后对线程同步模式进行了分类:用户模式与核心模式,接着详细说明了这两种同步模式的实现细节,并举例进行了对比。
1. [发现问题] 安装Articulate Presenter '09之后,打开时报错:Unable to find a version of the runtime to run this application. 错误标题是:.Net Framework Initialization Error。
2. [分析问题] 从错误提示来看,应该是.NET CLR版本兼容性的问题,我的机器上安装了VS2010,所以CLR应该是v4。而系统是Windows 7 64bit,应该自带了CLR v2,Presenter ’09可能不支持v4使用Visual Studio 2010 SDK的命令:ClrVer.exe验证了一下:
D:\Program Files (x86)\Microsoft Visual Studio 10.0\VC>clrver Versions installed on the machine:
v2.0.50727
v4.0.30319
3. [解决问题] 解决问题出人意料的顺利:在目标文件Presenter.exe所在的目录新建配置文件Presenter.exe.config:
<configuration>
<startup>
<supportedRuntime version="v2.0.50727"/>
</startup>
</configuration>
How Windows Performs I/O Operations
- Windows如何处理同步I/O操作?
- IRP:I/O Request Packet;
- 当硬件设备执行I/O操作时,发出I/O请求的线程无事可做,Windows会让该线程休眠所以并不会浪费CPU;
- Windows如何处理异步I/O操作?
- .NET异步文件I/O,注意要有FileOptions.Asynchronous,然后调用BeginRead();
- 完成的IRP从线程池中使用FIFO算法抽取;
- 当线程完成它的工作并返回到线程池中,线程池不会让它马上处理新的工作项直到CPUs再次饱和之后,因此减少了上下文切换而改进了性能。如果线程池稍后确定拥有的线程比实际需要的多,它会让这些多余的线程自行终结,回收这些线程占有的资源;
- 在CLR内部,线程池使用Windows资源I/O Completion Ports来实现异步I/O行为;
- SilverLight版本的FCL没有提供同步I/O操作,因为在浏览器进程中运行的缘故。
The CLR's Asynchronous Programming Model(APM)
- FCL中支持的APM的类型:
- 所有和硬件设备通讯的继承自System.IO.Stream的提供BeginWrite()和BeginRead()方法的类型;注意那些继承自System.IO.Stream但是不跟硬件设备通讯的类型,比如MemoryStream等,也提供了适合APM的BeginWrite()和BeginRead()方法,但是这些方法执行的是计算相关的操作,而不是I/O相关的操作,因此需要请求一个线程来执行计算相关的操作,而I/O相关的操作要少一个线程;
- System.Net.Dns提供了BeginGetHostAddress(), BeginGetHostByName(), BeginGetHostEntry(), BeginResolve();
- System.Net.Sockets.Socket类提供BeginAccept(), BeginConnect(), BeginDisconnect(), BeginReceive(), BeginReceiveFrom(), BeginReceiveMessageFrom(), BeginSend(), BeginSendFile(), BeginSendTo();
- 所有继承自System.Net.WebRequest的类型,提供了BeginGetRequestStream(), BeginGetResponse()方法;
- Sytem.IO.Ports.SerialPort有一个只读属性BaseStream,有BeginRead()和BeginWrite()方法;
- System.Data.SqlClient.SqlCommand类提供了BeginExecuteQuery(), BeginExecuteReader(), BeginExecuteXmlReader()。
- 每个BeginXxx()方法都有两个额外的参数:userCallback(:AsyncCallback委托类型)、stateObject(任何希望发送到userCallback的对象引用,在回调函数中,通过参数(IAsyncResult接口)的只读属性AsyncState来获得);
- 当调用BeginXxx()方法时,首先构造出一个唯一的对象标识出I/O请求,然后将该I/O请求加入到Windows设备驱动的队列中,接着返回一个IAsyncResult对象的引用。可以把这个对象看成是你异步调用的凭据。
The AsyncEnumerator Class
- APM的麻烦之处:
- 必须分隔代码到多个回调方法中;
- 必须避免使用参数和本地变量,因为这些变量没有分配在栈空间上,所以不能被其他线程和方法访问;
- 有些C#的代码结构中,比如try/catch/finally, using, for, do, while, foreach不能被使用;
- 有些特性比较难以实现,比如协调多个并发操作,支持取消和超时,通知GUI线程刷新控件等。
- AsyncEnumerator可以解决这些APM的麻烦,提供的特性有:
- 和现有的.NET技术简便集成;
- 协调多个异步并发操作;
- 支持分组取消;
- 通过调用每个操作的EndXxx()方法自动取消一组异步操作,如果不关心结果的话;
- 从烦心应用程序的线程模型中解放出来;
- 异常处理支持;
- 调试支持。
The APM and Exceptions
- HTTP(RFC 2616)申明客户端应用最多同时两个并发线程连接服务器。但是现在很多浏览器和服务器已经没有这个限制了;
- 如何在设计服务器程序时解除两个并发线程同时连接的限制:调整System.Net.ServicePointManager.DefaultConnectionLimit属性。
Applications and Their Threading Models
- GUI程序只允许创建窗体的线程更新界面控件;
- 当线程池线程开始处理客户端请求,可以假设客户端的文化特性,允许Web服务器返回文化特定的数字、日期、时间等;
- 通过查询GUI线程的.SynchronizationContext.Current属性,可以获得一个SynchronizationContext的引用,可以将该引用保存在共享变量中。这样无论什么时候需要GUI线程修改UI时,就可以让线程池线程引用保存的对象并调用Post方法,将该引用通过将要被GUI线程激发的方法。
- 推荐使用Post(内部BeginInvoke())方法,它将回调压入到GUI线程的队列并允许线程池线程立即返回。而Send内部Invoke()方法,不会立即返回,会被阻塞;
- ASP.NET Web窗体和XML Web服务程序,处理客户端请求开始运行的线程,会有一个继承自SynchronzationContext的对象跟它相关联。
Implementing a Server Asynchronously
- 如何实现异步的ASP.NET Web窗体页面,在.aspx文件的头部加入Page指示"Async=true"。然后在代码中调用AddOnPreRenderCompleteAsync方法;
- 如何实现异步的ASP.NET Web服务,提供两个方法BeginXxx(),EndXxx(),并加上属性标签[WebMethod];
- 如何实现异步的WCF Web服务,定义符合模式的BeginXxx(), EndXxx()方法,然后用[OperationContract(AsyncPattern=true)]属性标签标记BeginXxx()。
The APM and Compute-Bound Operations
- I/O请求会压入到Windows设备驱动的队列中。然而,委托的BeingInvoke方法通过内部调用TheadPool.QueueUserWorkItem()将计算方向的操作压入到CLR的线程池队列。
APM Considerations
- APM要注意的问题:
- 不通过线程池使用APM;
- APM提供三种途径来知晓异步操作是否完成:
- 线程在操作完成之前传入IAsyncResult对象调用EndXxx();
- 一个线程可以被阻塞等待其他操作完成,通过调用WaitOne()(IAsyncResult.AsyncWaitHandle);
- 线程可以连续的查询IAsyncResult.IsCompleted属性。
- 总是调用EndXxx()方法,并只调用一次。否则会有资源泄露;
- 要求调用EndXxx()有两个原因:
- 当你初始化一个异步操作时CLR会分配一些内部资源。当操作完成后,CLR会持有这些资源知道EndXxx()被调用;
- 当初始化一个异步操作,你实际上并不知道该操作最终是成功还是失败。发现问题的唯一途径是调用EndXxx()查询返回值或者检查异常。
- 调用EndXxx()方法时总是使用同样的对象;
当调用BeginInvoke时,IAsyncResult对象内部会持有一个引用到最初对象。 - 在调用BeginXxx()和EndXxx()方法时使用ref, out和params参数;
- 不能取消一个异步的I/O方向的操作;
- 内存消耗:如果你知道所要执行的I/O操作能够很快完成,那么以同步方式执行更合适;
- 有些I/O操作必须以同步方式完成。比如Win32的CreateFile(调用FileStream的构造器)总是以同步方式执行;
Windows Vista,微软引入了新的Win32函数CancelSynchronousIO()。该函数允许线程取消一个被其他线程执行的同步I/O操作。
Windows没有提供异步访问注册表、访问系统事件日志、获得目录/子目录、修改文件/目录的属性、……的函数。 - FileStream特定的问题。
FileOptions.Asynchronous标志(等效于Win32的CreateFile的FILE_FLAG_OVERLAPPED标志):如果没有指定该标志,Windows以同步方式执行所有的文件操作。当然,仍然可以调用FileStream.BeginRead()方法,看上去操作以异步方式执行,但在内部,FileStream使用另外的线程来模拟异步操作。
当使用FileStream,必须确定是以同步还是异步方式执行,也就是FileOptions.Asynchronous标志。如果指定该标志,总是调用BeginRead(),如果不指定,总是调用Read()。
- 不通过线程池使用APM;
I/O Request Priorities
- 低优先级的线程获得时间后,可以在短时间内排队大量的I/O请求,所以呢低优先级的线程可能会挂起高优先级的线程,从而严重影响系统的响应性;
- 关于I/O优先级,可以从这里下载I/O Prioritization in Windows Vista白皮书;
- 相关的Win32 API:GetCurrentProcess、GetCurrentThread、CancelSynchronousIo;
- 进程只能影响他自己的后台处理模式;Windows不允许线程修改其他进程的后台处理模式;
- 优先级反转:低优先级的线程在正常优先级的线程等待时抢得了线程同步锁,正常优先级的线程可能最终等待后台优先级的线程直到低优先级I/O请求完成。
Converting the IAsyncResult APM to a Task
- I/O-Bound 从APM转换到Task:
APM 代码片段:
WebRequest webRequest = WebRequest.Create("http://wintellect.com"); webRequest.BeginGetResponse(result => { WebResponse webResponse = null; try { webResponse = webRequest.EndGetRespone(result); Console.WriteLine("Content Length: " + webResponse.ContentLength); } catch (WebException we) { Console.WriteLine("Failed: " + we.GetBaseException().Message); } finally { if (webResponse != null) webResponse.Close(); } }, null);Task 代码片段:
WebRequest webRequest = WebRequest.Create("http://wintellect.com"); Task.Factory.FromAsync<WebResponse>( webRequest.BeginGetResponse, webRequest.EndGetRespone, null, TaskCreationOptions.None) .ContinueWith(task => { WebResponse webResponse = null; try { webResponse = task.Result; Console.WriteLine("Content Length: " + webResponse.ContentLength); } catch (AggregateException ae) { if (ae.GetBaseException() is WebException) Console.WriteLine("Failed: " + ae.GetBaseException().Message); else throw; } finally { if (webResponse != null) webResponse.Close(); } });
The Event-Based Asynchronous Pattern
- EAP:Event-based Asynchronous Pattern;
- BackgroundWorker是设计用来处理计算方向的异步工作的,别错误的用来完成I/O方向的异步工作。I/O方向的比计算方向的异步操作可以少一个线程;
- BackgroundWorker提供三个(EAP)事件:DoWork、ProgressChanged、RunWorkerCompleted;
- 将EAP转换成Task:System.Threading.Tasks.TaskCompletionSource;
- 比较APM和EAP:
- EAP比APM最大的优点就是Visual Studiao设计时支持。GUI程序,事件处理方法由GUI线程激发;
- EAP内部以APM方式实现,需要更多的内存并且执行速度相对较慢。实际上,EAP必须为所有的过程报告和完成事件分配多个继承自EventArg的对象;
- 以后,静态方法和单例类不再能提供EAP,因为:程序的不同部分可以立刻注册所有的事件,当操作完成时,程序的不同部分无论是否异步操作都需要激发事件处理方法;
- EAP一般用于窗体程序,而且由于有额外开销,虽然在设计器上可以方便使用,但是还是不建议使用。
Programming Model Soup
- 比较:QueueUserWorkItem、Timer、RegisterWaitForSingleObject、Tasks、IAsyncResult APM、Event-based PM、AsyncEnumerator;
- Tasks比QueueUserWorkItem或委托的BeginInvoke性能更好;
- 对于ThreadPool.QueueUserWorkItem或委托的BeginInvoke,使用PreFairness标志可以获得相同的线程池行为;
- 可以定制TaskScheduler,可以改变调度算法而不需要改变代码或编程模型;
- Task对象比QueueUserWorkItem或委托的BeginInvoke会消费更多的内存;
- IAsyncResult APM提供了四种技术,模型比较复杂,但是如果少使用回调方法技术,模型就简单多了;
- 支持EAP的类多半都支持取消我行为;
- IAsyncResult APM支持不支持任何取消操作;
- EAP是基于事件的,因此可以简单的用于Windows窗体、WPF、SilverLight窗体设计器。
本章小结
本章详细讲述了I/O方向的异步工作如何完成,首先解释了Windows处理同步I/O和异步I/O的内部机制。然后介绍了APM,针对APM的问题还介绍了一个Wintellect的辅助类AsyncEnumerator。接着讲了APM如何处理异常,以及应用程序如何选择线程模型。演示了如何实现一个异步的服务器程序(ASP.NET WEB服务、ASP.NET窗体页面、WCF)。解释了APM还可以用于计算方向的异步操作。接着,重点解释了APM的缺点以及处理办法。然后讲述了I/O请求的优先级,以及如何讲了IAsyncResult APM转换成Task。接着还介绍了一种不太优雅的异步编程模型EAP。最后针对几种异步编程模型进行了比较。
Introducing the CLR's Thread Pool
- 线程池可以看成有一组线程可以被你的应用程序以自有的方式使用;
- 每个CLR有一个线程池,这个线程池被该CLR控制的AppDomain共享;
- 当CLR初始化时,线程池中没有线程。线程池内部维护一个操作请求的队列;
- 线程池最重要的作用是管理拥有多少线程才算合适,少点线程以避免浪费资源,多点线程以发挥多处理器、超线程处理器、多核处理器的优势;
- 工作者线程(Worker Threads)用来当应用程序请求线程池来执行异步的计算方向的操作(也可以初始化一个I/O方向的操作);
- I/O线程(I/O Threads)用来通知你的代码一个异步的I/O操作已经完成;
- APM:Asynchronous Programming Model。
Performing a Simple Compute-Bound Operation
- TheadPool.QueueUserWorkerItem(WaitCallback callback), .QueueUserWorkerItem(WaitCallback callback, Object state);
- 如果回调方法抛出一个异常没有被处理,CLR终止该进程(除非宿主强制使用自己的策略)。
Execution Contexts
- 每个线程都有一个相关联的执行上下文(Execution Context)数据结构,包括如安全设置(Thread.Principal)、宿主设置(System.Threading.HostExecutionContextManager)、逻辑调用上下文数据(Sytem.Runtime.Remoting.Messaging.CallContext.LogicalSetData(). LogicalSetData());
- ExecutionContext类允许你控制一个线程的Execution Context从一个线程产生自另一个线程;
- ExecutionContext.SuppressFlow()方法标记有[SecurityCritical]属性,因此一些客户程序(比如SilverLight)不能调用;
- 流转包含逻辑调用上下文数据项的执行上下文会戏剧性的影响性能,因为需要序列化/反序列化这些数据项;
- 在执行ExecutionContext.SuppressFlow()之后,CallContext.LogicalSetData()设置的上下文数据就不能被访问了。
Cooperative Cancellation
- 合作取消模式,意味着你希望取消的操作必须支持显示的取消行为;
- 要在线程池中取消一个操作,必须使用System.Threading.CancellationTokenSource对象。实例化CancellationTokenSource对象后,该实例有一个Token属性;
- 可以注册(CancellationToken.Register())一个或多个方法,当一个CancellationTokenSource取消后被调用;
- 如何连接多个CancellationTokenSource对象,调用CancellationTokenSource.CreateLinkedTokenSource()静态方法。
Tasks
- 使用ThreadPool.QueueUserWorkerItem()虽然简单,但是有限制,最大的问题是不知道何时操作完成,也没有办法获得返回值;
- 更好的解决方案是使用Tasks,System.Threading.Tasks;
- [Flags]public enum TaskCreationOptions {None, PreferFairness, LongRunning, AttachedToParent};
- 等待一个Task完成并获得返回结果:Task.Wait()实例方法, Task.Result属性;
- 如果调用了Wait(),但是Task没有开始执行,系统可能(依赖TaskScheduler)使用调用Wait()的线程执行该Task,这样线程调用Wait()不会被阻塞;立即执行Task然后立即返回,这可能会节约资源提高性能,但是可能会造成死锁;
- 使用TaskScheduler.UnobservedTaskException静态事件注册回调方法,可以帮组检测未观测到的异常;
- Task.WaitAny()静态方法阻塞调用线程直到数组中的任何Task对象完成;
- Task.WaitAll()静态方法阻塞调用线程直到数组中的所有Task对象完成;
- 取消一个Task:Token.ThrowIfCancellationRequested();
- 如果CancellationToken在Task开始之后被调度之前取消,Task不执行就取消。但是如果不开始就取消,会抛出InvalidOperationException异常;
- 一旦一个Task对象有CancellationToken关联上,就没有途径访问它,所以你必须以某种方式获得相同的CancellationToken;
- 当一个Task完成时自动开始另一个Task:Task.ContinueWith()实例方法;
- [Flags]public enum TaskContinuationOptions {None, PreferFairness, LongRunning, AttachedToParent, ExecuteSynchronously, NotOnRanToCompletion, NotOnFaulted, NotOnCanceled, OnlyOnCanceled, OnlyOnFaulted, OnlyOnRanToCompletion};
- 一个Task开始多个Child Tasks:Task
; - public enum TaskStatus {Created, WaitingForActivation, WaitingToRun, Running, WaitingForChildrenToComplete, RanToCompletion, Canceled, Faulted};
- TaskFactory
, .StartNew(); - 两类TaskScheduler:
- 线程池任务调度器:默认情况下,所有的应用程序使用线程池任务调度器;
- 同步上下文任务调度器:通常用于Windows Forms、WPF、SilverLight等GUI应用;
- Parallel Extensions Extras包:
- IOTaskScheduler
- LimitedConcurrencyLevelTaskScheduler
- OrderedTaskScheduler
- PrioritizingTaskScheduler
- ThreadPerTaskScheduler
Parallel's Static For, ForEach, and Invoke Methods
- 如果工作必须以顺序方式完成,那么就不要使用Parallel方法;同样,避免工作项修改任何类型的共享数据,因为如果多个线程同时维护该数据可能会出现问题;
- Parallel.For(), .ForEach(), .Invoke()方法可以接受ParallelObject对象参数;
- Parallel.For(), .ForEach()的重载方法可以传入3个委托:
- 任务本地初始化委托(localInit),在任务请求处理一个工作项前调用;
- 主体部分委托(body),一旦有项目被参与工作的线程处理时被调用;
- 任务本地终止委托(localFinally),任务处理完所有的工作项后调用,即使遇到异常。
Parallel Language Integrated Query
- 使用LINQ,可以简便的进行过滤、排序、投影等操作。但是这些处理过程是顺序的;
- Parallel LINQ,以并行的方式使用LINQ。System.LINQ.ParallelQuery
; - 要使用Parallel LINQ,必须使用ParallelEnumerable.AsParallel()扩展方法从顺序查询(基于IEnumerable或IEnumerable
)转换成并行查询(基于ParallelQuery或ParallelQuery ); - 如果需要让Parallel LINQ保持处理的顺序,需要调用ParallelEnumerable.AsOrdered()扩展方法;
- 以无序方式处理的行为:Distinct, Except, Intersect, Union, Join, GroupBy, GroupJion, ToLookup;
- 以有序方式处理的行为:OrderBy, OrderbyDescending, ThenBy, ThenByDescending;
- WithDegreeOfParallelism()方法指定同时处理的最多的线程数量。缺省情况下,线程数量等于CPU核的数量;
- Parallel LINQ会分析查询然后决定如何处理最佳;
- 有时候顺序处理会获得最佳性能,比如:Concat, ElementAt(OrDefault), First(OrDefault), Last(OrDefault), Skip(While), Take(While), Zip。以及在选择器(selector)和断言(predicate)委托中含有位置指示的Select(Many)和Where;
- 如果你希望强制并行方式处理,可以调用WithExecutionMode(),传入ParallelExecutionMode {Default, ForceParallelism}标志;
- WithMergeOptions(),可以用来控制查询结果集合并缓存,也就是时空权衡,传入ParallelMergeOptions {Default, NoBuffered, AutoBuffered, FullyBuffered}标志;
Performing a Periodic Compute-Bound Operation
- System.Threading.Timer,构造时可以有TimerCallback回调参数;
- 当一个Timer对象被当作垃圾收集后,它的终止化方法告诉线程池取消该定时器因此不再离开。因此当使用Timer对象时,确定有变量保持Timer对象存活否则你的回调方法将停止调用;
- 有很多的定时器,之间有什么差异呢?
- System.Threading.Timer,最好的定时器,如果要执行间隙性的后台任务,首选它;
- System.Windows.Forms.Timer,类似于Win32的SetTimer函数,当定时器消失时,Windows会插入一个WM_TIMER在线程的消息队列中。不能被多线程同时执行。回调方法的线程必须和设置定时器的线程是同一线程;
- System.Threading.DispatcherTimer,用于WPF和SilverLight,等同于System.Windows.Forms.Timer;
System.Timer.Timer,唯一的好处就是可以用于在设界面计器上拖拽,除非真的有必要,否则就不要用。
How the Thread Pool Manages Its Threads
- 线程池的默认最多线程数量是1000,这在32位系统2G的内存地址空间下是比较合理的限制。CLR项目组会进行合理调整,不要自行修改;
- System.Threading.ThreadPool.GetMaxThreads(), .SetMaxThreads(), .GetMinThreads(), .SetMinThreads(), .GetAvailableThreads();
- CLR的线程池有一个全局队列,每个工作线程有一个本地队列;
- 全局队列采用FIFO算法;本地队列采用LIFO算法。
Cache Lines and False Sharing
- CPU逻辑上将内存划分为缓存行(cache line),在64位的机器系统上,一个缓存行包含64字节,因此CPU以64字节块对RAM进行存取;
- 因此,如果是64位的机器系统,需要读取Int32的变量,那么包含该变量(4Bytes)的64Bytes的内存块会被读取。读取更多的字节可能会有很好的性能提升,因为大多数应用程序趋向于访问临近的数据,这样数据就很可能已经在Cache中,不需要再从RAM中读取;
- 但是,如果如果两核或多核访问的数据在同一个缓存行上,这些CPU核就需要进行协调,反而会极大的对性能造成负面影响;
- Win32的GetProcessorInformation()函数可以获得CPU的缓存行大小;
- [StructLayout(LayoutKind.Explicit)]和[FieldOffest(...)]以缓存行大小递增可以控制字段的布局,以避免需要多线程访问的临近数据不在同一个缓存行上;
- 因为内存布局和缓存行的缘故。从程序的观点,两个线程管理不同的数据。但是从CPU的观点,两个CPU核却是管理相同的数据。这叫做:虚假共享(False Sharing);
- 如果这些CPU在不同的NUMA节点上,虚假共享(False Sharing)会对性能造成极其负面的影响;
- 要避免虚假共享(False Sharing),应该避免一个线程写数组的前几个元素而其他的线程在访问数组的其他元素。
本章小结
本章讲了计算方向的异步操作问题,首先隆重推出了CLR的线程池概念,给线程池的线程划分了两个类别:工作者线程和I/O线程。然后演示了如何简单的使用线程池实现一个多线程计算操作;接着讨论了线程池的线程执行体上下文结构;然后引出了协作式取消模式以解释如何在线程池中取消操作;本章重点讨论了Task,一种使用线程池的更强大易用方法,并演示了如何等待任务完成获得结果,如何取消任务,如何在一个任务完成后自动的开始另外一个任务,如何由一个任务开始子任务。接着分析了Task的实现机制和调度策略,如何利用TaskFactory工厂模式创建任务。本章还讲述了如何使用Parallel LINQ以及它的限制和注意事项;接着还讨论了定时器的作用,比较了FCL提供的几种定时器的差异。之后简单解释了线程池是如何管理线程的。最后讨论了高速缓存行和多处理器环境下需要注意的错误共享(False Sharing)问题。