热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

c#线程初探(二)

继续上一篇c#线程初探(一),这里介绍线程同步的常见概念和注意事项。3、同步使用线程的一个重要方面是同步访问多个线程访问的任何变量。(1)、“同步”:所谓同步,是指在某一时刻只有一个线程可以访问变量。

继续上一篇c#线程初探(一),这里介绍线程同步的常见概念和注意事项。
3、同步
使用线程的一个重要方面是同步访问多个线程访问的任何变量。
(1)、“同步”:所谓同步,是指在某一时刻只有一个线程可以访问变量。
同步问题只会发生在下述场景:至少有一个线程要写入一个变量,而与此同时,其他线程正在读取或者写入同一个变量。这和大学课程《操作系统》教的线程同步是一个道理。
c#为同步访问变量提供了一种非常简单的方式,即使用关键字lock。Code is cheap.举例来说,在“保证一个类仅有一个实例:单例模式”就已经用了这种方式:

Code
using System;
using System.Threading;
/// 
/// Singleton 
/// 

public class WindowsTaskManager
{
    
private static WindowsTaskManager wtm;
    
private static readonly object syncRoot = new object();// 程序运行时创建一个静态只读的进程辅助对象
    private WindowsTaskManager()
    {
    }
    
public static WindowsTaskManager CreateSingleWtm()
    {
        
lock (syncRoot) //lock 同步访问变量
        {
            
if (wtm == null)
            {
                wtm 
= new WindowsTaskManager();
            }
        }
        
return wtm;
    }
}

lock语句把变量放在括号中,以包装对象,被称为独占锁或者排它锁。当执行带有lock关键字的复合语句时,独占锁会保留下来。当变量被包装在独占锁中时,其他线程就不能访问该变量。如果在上面代码中使用独占锁,在执行复合语句时,这个线程就会失去其“时间片”。如果下一个获得时间片的线程试图访问变量syncRoot,就会被拒绝。Windows会让其他线程处于睡眠状态,直到解除了独占锁为止。
PS:上述代码中,我们lock的是一个object对象,对于string这个特殊类型的对象,我们一定要慎用。比如下面的代码:

Code
   class LockTestOne
    {
        
private string strSync = "";
        
public void DoSomething()
        {
            
lock (strSync)
            {
                
//do something
            }
        }
    }

    
class LockTestTwo
    {
        
private string strSync2 = "";
        
public void DoSomething2()
        {
            
lock (strSync2)
            {
                
//do something
            }
        }
    }

本来,LockTestOne和LockTestOne2类中各自的方法一点关系没有,但在这里,DoSomething2方法执行时,若另一个线程在执行DoSomething方法,那它得等待!两个变量(strSync和strSync2)都是在编译时赋值为""(空字符串),.NET会让这两个变量指向同一个拘留池的对象(strSync和strSync2在拘留池中。)。于是,两个lock看似lock两个毫不相干的对象,但其实是在lock同一个对象。所以,准确的讲,我们不要lock拘留池中的字符串。
(2)同步引起的问题:死锁(dead lock)和竞态条件(race condition)
线程同步非常重要,但是要慎用,因为这会降低性能。原因有两个,首先,在对象上放置和解开锁会带来某些系统开销。第二个原因更重要,线程使用的越多,等待释放对象的线程就越多。如果一个线程在对象上放置了一个锁,需要访问该对象的其他线程就只能暂停执行,直到该锁被解开才能继续执行。因此,在lock块内编写的代码越少越好,以免出现线程同步错误。lock语句某种意义上就是临时禁用应用程序的多线程功能,也就删除了多线程的各种优势。
使用线程同步有潜在的危险,主要表现就是死锁和竞态条件。
a、死锁:死锁是一个错误,在两个线程都需要访问该被互锁的资源时发生。比如下面的代码:

Code
        /* 线程1运行如下代码 */
        
//a和b是两个线程都可以访问的对象引用
        lock (a)
        {
            
//do something
            lock (b)
            {
                
//do something
            }
        }

        
/* 线程2运行如下代码 */
        
lock (b)
        {
            
//do something
            lock (a)
            {
                
//do something
            }
        }

在上面代码中,根据线程1和线程2遇到不同语句的时间,可能会出现下述情况:线程1在a上加锁,同时线程2在b上加锁。不久,线程1开始遇到lock(b)语句,立即进入睡眠状态,等待b上的锁被释放。之后,第二个线程遇到lock(a)语句,也立即进入睡眠状态,等待a上的锁被释放。但是,a上的锁永远不会解开,因为线程1拥有这个锁,目前正处于睡眠状态,在b上的锁被解开前是不会“醒过来”的。而在线程2被叫醒之前,b上的锁不会解开,这样线程1和线程2就互相等待对方释放资源(最后就耗上了),这样就形成一个死锁。
解决死锁的方法:让这两个线程以相同的顺序在对象上声明加锁。正确的代码如下:

Code
       /* 线程1运行如下代码 */
        
//a和b是两个线程都可以访问的对象引用
        lock (a)
        {
            
//do something
            lock (b)
            {
                
//do something
            }
        }

        
/* 线程2运行如下代码 (对a和b加锁顺序和线程1一样)*/
        
lock (a)
        {
            
//do something
            lock (b)
            {
                
//do something
            }
        }

b、竞态条件
竞态条件比死锁更微妙。它很少中断进程的执行,但可能导致数据损坏。当几个线程视图访问同一个数据,但没有考虑其他线程的执行情况时,就会发生竞态条件。
注:关于竞态条件不是一两句话就可以说的清楚的,读者可以参考相关资料,大学教材《操作系统》有详细讲解,这里不在赘述了。


推荐阅读
  • VB.net 进程通信中FindWindow、FindWindowEX、SendMessage函数的理解
    目录一、代码背景二、主要工具三、函数解析1、FindWindow:2、FindWindowEx:3、SendMessage: ... [详细]
  • 开发日志:201521044091 《Java编程基础》第11周学习心得与总结
    开发日志:201521044091 《Java编程基础》第11周学习心得与总结 ... [详细]
  • C# .NET 4.1 版本大型信息化系统集成平台中的主从表事务处理标准示例
    在C# .NET 4.1版本的大型信息化系统集成平台中,本文详细介绍了主从表事务处理的标准示例。通过确保所有操作要么全部成功,要么全部失败,实现主表和关联子表的同步插入。主表插入时会返回当前生成的主键,该主键随后用于子表插入时的关联。以下是一个示例代码片段,展示了如何在一个数据库事务中同时添加角色和相关用户。 ... [详细]
  • 零拷贝技术是提高I/O性能的重要手段,常用于Java NIO、Netty、Kafka等框架中。本文将详细解析零拷贝技术的原理及其应用。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 本文介绍了如何利用 Delphi 中的 IdTCPServer 和 IdTCPClient 控件实现高效的文件传输。这些控件在默认情况下采用阻塞模式,并且服务器端已经集成了多线程处理,能够支持任意大小的文件传输,无需担心数据包大小的限制。与传统的 ClientSocket 相比,Indy 控件提供了更为简洁和可靠的解决方案,特别适用于开发高性能的网络文件传输应用程序。 ... [详细]
  • 本文深入探讨了Java多线程环境下的同步机制及其应用,重点介绍了`synchronized`关键字的使用方法和原理。`synchronized`关键字主要用于确保多个线程在访问共享资源时的互斥性和原子性。通过具体示例,如在一个类中使用`synchronized`修饰方法,展示了如何实现线程安全的代码块。此外,文章还讨论了`ReentrantLock`等其他同步工具的优缺点,并提供了实际应用场景中的最佳实践。 ... [详细]
  • 如何在C#中配置组合框的背景颜色? ... [详细]
  • 本文探讨了如何在C#应用程序中通过选择ComboBox项从MySQL数据库中检索数据值。具体介绍了在事件处理方法 `comboBox2_SelectedIndexChanged` 中可能出现的常见错误,并提供了详细的解决方案和优化建议,以确保数据能够正确且高效地从数据库中读取并显示在界面上。此外,还讨论了连接字符串的配置、SQL查询语句的编写以及异常处理的最佳实践,帮助开发者避免常见的陷阱并提高代码的健壮性。 ... [详细]
  • 本文深入探讨了C#中的反射与特性功能。首先,介绍了反射的基本概念,即通过元数据(包括类的方法、属性和字段等)在运行时动态获取和操作程序信息的能力。此外,还详细解析了特性的使用方法及其在代码注解和元数据扩展中的重要作用,为开发者提供了丰富的编程技巧和实践指导。 ... [详细]
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
  • 本文详细介绍了在C#编程环境中绘制正方形图像的技术和实现方法,通过具体示例代码帮助读者理解和掌握相关技巧。内容涵盖从基础概念到实际应用的各个方面,适合初学者和有一定经验的开发者参考。希望对您的C#学习之旅有所帮助,并激发您进一步探索的兴趣。 ... [详细]
  • C#编程中按钮控件的使用与优化 ... [详细]
  • 探索偶数次幂二项式系数的求和方法及其数学意义 ... [详细]
author-avatar
手机用户2502911283
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有