以前常听高手告诫MFC对象不要跨线程使用,因为MFC不是线程安全的。比如CWnd对象不要跨线程使用,可以用窗口句柄(HWND)代替。 CSocket/CAsyncSocket对象不要跨线程使用,用SOCKET句柄代替.
那么到底什么是线程安全呢?什么时候需要考虑?如果程序涉及到多 线程的话,就应该考虑线程安全问题。比如说设计的接口,将来需要在多线程环境中使用,或者需要跨线程使用某个对象时,这个就必须考虑了。个人的理解是,所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不 用考虑同步的问题。
一般而言“线程安全”由多线程对共享资源的访问引起。下面是几个常见的情况,
线程不安全的情况:
1,调用某个接口时需要我们自己采取同步措施来保护该接口访问的共享资源
线程安全的情况:
1,接口中访问的数据都属于私有数据
2,几个接口对共享数据都是只读操作
3,多个接口之间有共享数据,而且有读有写的话,如果设计者自己采取了同步措施,调用者不需要考虑数据同步问题
下面以c#为例说明如果线程安全使用的情况。
在多线程编程中经常需要在线程间共享资源。例如,多个线程可能需要访问一个共享数据库,或对一组系统变量进行更新。当多个线程同时竞争共享资源的访 问权时,就可能会出现“争用状态”。如果一个线程将资源修改为无效状态,然后另一个线程又试图访问该资源并在无效状态中使用该资源,此时便存在争用状态。
考虑下面的示例:
{
public int TotalWidgets = 0;
public void AddWidget()
{
TotalWidgets++;
Console.WriteLine("Total widgets = " + TotalWidgets.ToString());
}
public void RemoveWidgets()
{
TotalWidgets -= 10;
}
}
此类公开两个方法。一个方法 (AddWidget) 将 1 添加到 TotalWidgets 字段并将该值写入控制台。第二个方法从 TotalWidgets 的值减 10。假设两个线程同时试图访问 WidgetManipulator 类的同一个实例,请考虑将发生什么情况。在一个线程调用 AddWidget 的同时另一个线程可能调用 RemoveWidgets。这种情况下,TotalWidgets 的值可能会在第一个线程报告准确值之前就被第二个线程更改。这种争用状态可能导致报告的结果不准确并可能导致数据损坏。
使用锁来防止争用状态
您可以通过使用“锁”来保护代码的关键部分免受争用状态的损坏。锁(在 C# 中用关键字lock语句表示)允许单个执行线程获得对某个对象的独占执行权限。下面的示例对锁进行了演示:
{
System.Object lockThis = new System.Object();
lock(lockThis)
{
// Access thread-sensitive resources.
}
}
当遇到锁时,对指定对象(在上例中为 MyObject)的执行将被阻塞,直到该线程获得对该对象的独占访问权为止。当达到锁的末尾时,锁将被释放,执行继续正常进行。您只能在返回引用的对象上获得锁。不能用这种方式锁定值类型。
锁的缺点
虽 然使用锁可以保证多个线程不会同时访问一个对象,但是它们可能会导致明显的性能降低。假设正在运行一个包含多个不同线程的程序。如果每个线程都需要使用一 个特定的对象并且必须等待到获得该对象的专用锁后才能执行,则这些线程都将停止执行,一个排在另一个的后面,从而导致性能降低。由于上述原因,只应该在具 有必须作为一个单元执行的代码时使用锁。例如,您可能更新相互依赖的多个资源。这种代码称作“原子”代码。通过将锁的使用范围限制在必须以原子方式执行的 代码,就可以编写既能确保代码安全又能保持良好性能的多线程组件。
还要小心地避免可能发生“死锁”的情况。在发生这种情况时,多个线程彼 此都等待对方释放共享资源。例如,线程 1 可能拥有对资源 A 的锁,而在等待资源 B。而另一方面,线程 2 可能拥有对资源 B 的锁,而在等待资源 A。这种情况将使得两个线程都无法进行。避免死锁情况的唯一方法是仔细编程。