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


推荐阅读
  • 介绍平常在多线程开发中,总避免不了线程同步。本篇就对net多线程中的锁系统做个简单描述。目录一:lock、Monitor1:基础 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 本文详细介绍了使用C#实现Word模版打印的方案。包括添加COM引用、新建Word操作类、开启Word进程、加载模版文件等步骤。通过该方案可以实现C#对Word文档的打印功能。 ... [详细]
  • PriorityQueue源码分析
     publicbooleanhasNext(){returncursor<size||(forgetMeNot!null&am ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 本文介绍了使用Spark实现低配版高斯朴素贝叶斯模型的原因和原理。随着数据量的增大,单机上运行高斯朴素贝叶斯模型会变得很慢,因此考虑使用Spark来加速运行。然而,Spark的MLlib并没有实现高斯朴素贝叶斯模型,因此需要自己动手实现。文章还介绍了朴素贝叶斯的原理和公式,并对具有多个特征和类别的模型进行了讨论。最后,作者总结了实现低配版高斯朴素贝叶斯模型的步骤。 ... [详细]
  • JVM:33 如何查看JVM的Full GC日志
    1.示例代码packagecom.webcode;publicclassDemo4{publicstaticvoidmain(String[]args){byte[]arr ... [详细]
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社区 版权所有