多线程之线程同步

2023-11-09

多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程;其二是线程同步问题,鄙人现在学习与探究的是线程同步问题。

通过学习《CLR via C#》里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造。所谓基元则是在代码中使用最简单的构造。基元构造又分成两类,一个是用户模式,另一个是内核模式。而混合构造则是在内部会使用基元构造的用户模式和内核模式,使用它的模式会有一定的策略,因为用户模式和内核模式各有利弊,混合构造则是为了平衡两者的利与弊而设计出来。下面则列举整个线程同步体系结构

  1. 基元

    1.1 用户模式

    1.1.1 volatile

    1.1.2 Interlock

    1.2 内核模式

    1.2.1 WaitHandle

    1.2.2 ManualResetEvent与AutoResetEvent

    1.2.3 Semaphore

    1.2.4 Mutex

  2. 混合

    2.1 各种Slim

    2.2 Monitor

    2.3 MethodImplAttribute与SynchronizationAttribute

    2.4 ReaderWriterLock

    2.5 Barier(少用)

    2.6 CoutdownEvent(少用)

   

先从线程同步问题的原因说起,当内存中有一个整形的变量A,里面存放的值是2,当线程1执行的时候它会把A的值从内存中取出存放到CPU的寄存器中,并把A赋值为3,此时刚好线程1的时间片结束;接着CPU把时间片分给线程2,线程2同样把A从内存中的值取出来放到内存中,但是由于线程1并没有把变量A的新值3放回内存,故线程2读到的仍然是旧的值(也就是脏数据)2,然后线程2要是需要对A值进行一些判断之类的就会出现一些非预期的结果了。

而针对上面这种对资源的共享问题处理,往往会使用各种各样办法。下面则逐一介绍

   

先说说基元构造中的用户模式,凡是用户模式的优点是它的执行相对较快,因为它是通过一系列CPU指令来协调,它造成的阻塞只是极短时间的阻塞,对操作系统而言这个线程是一直在运行,从未被阻塞。缺点就是唯有系统内核才能停止这样的一个线程运行。另一方面就是由于线程在自旋而非阻塞,那么它还会占用这CPU的时间,造成对CPU时间的浪费。

首先是基元用户模式构造中的volatile构造,这个构造网上很多说法是让CPU对指定字段(Field,也就是变量)的读都是从内存读,每次写都是往内存写。然而它和编译器的代码优化有关系。先看看如下代码

    public class StrageClass
    {
        volatile int mFlag = 0;
        int mValue = 0;
 
        public void Thread1()
        {
            mValue = 5;
            mFlag = 1;
        }
 
        public void Thread2()
        {
            if (mFlag == 1)
                Console.WriteLine(mValue);
        }
    }

 

在懂得多线程同步问题的同学们都会知道如果用两个线程分别去执行上面两个方法时,得出的结果有两个:1.不输出任何东西;2.输出5。但是在CSC编译器编译成IL语言或JIT编译成机器语言的过程中,会进行代码优化,在方法Thread1中,编译器会觉得给两个字段赋值会没什么所谓,它只会站在单个线程执行的角度来看,完全不会顾及多线程的问题,因此它有可能会把两行代码的执行顺序调乱,导致先给mFlag赋值为1,再给mValue赋值为5,这就导致了第三种结果,输出0。可惜这种结果我一直无法测试出来。

解决这个现象的就是volatile构造,使用了这种构造的效果是,凡是对使用了此构造的字段进行读操作时,该操作都保证在原有代码顺序下会在最先执行;或者是凡是对使用了此构造的字段进行写操作时,该操作都保证在原有代码顺序下会在最后执行。

实现了volatile的构造现在来说有三个,其一是Thread的两个静态方法VolatileRead和VolatileWrite,在MSND上的解析如下

Thread.VolatileRead 读取字段值。 无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。

Thread.VolatileWrite 立即向字段写入一个值,以使该值对计算机中的所有处理器都可见。

在多处理器系统上, VolatileRead 获得由任何处理器写入的内存位置的最新值。 这可能需要刷新处理器缓存;VolatileWrite 确保写入内存位置的值立即可见的所有处理器。 这可能需要刷新处理器缓存。

即使在单处理器系统上, VolatileRead 和 VolatileWrite 确保值为读取或写入内存,并不缓存 (例如,在处理器寄存器中)。 因此,您可以使用它们可以由另一个线程,或通过硬件更新的字段对访问进行同步。

从上面的文字看不出他和代码优化有任何关联,那接着往下看。

volatile关键字则是volatile构造的另外一种实现方式,它是VolatileRead和VolatileWrite的简化版,使用 volatile 修饰符对字段可以保证对该字段的所有访问都使用 VolatileRead 或 VolatileWrite。MSDN中对volatile关键字的说明是

volatile 关键字指示一个字段可以由多个同时执行的线程修改。 声明为 volatile 的字段不受编译器优化(假定由单个线程访问)的限制。 这样可以确保该字段在任何时间呈现的都是最新的值。

从这里可以看出跟代码优化有关系了。而纵观上面的介绍得出两个结论:

1.使用了volatile构造的字段读写都是直接对内存操作,不涉及CPU寄存器,使得所有线程对它的读写都是同步,不存在脏读了。读操作是原子的,写操作也是原子的。

2.使用了volatile构造修饰(或访问)字段,它会严格按照代码编写的顺序执行,读操作将会在最早执行,写操作将会最迟执行。

最后一个volatile构造是在.NET Framework中新增的,里面包含的方法都是Read和Write,它实际上就相当于Thread的VolatileRead 和VolatileWrite 。这需要拿源码来说明了,随便拿一个Volatile的Read方法来看

而再看看Thraed的VolatileRead方法

   

另一个用户模式构造是Interlocked,这个构造是保证读和写都是在原子操作里面,这是与上面volatile最大的区别,volatile只能确保单纯的读或者单纯的写。

为何Interlocked是这样,看一下Interlocaked的方法就知道了

Add(ref int,int)// 调用ExternAdd 外部方法

CompareExchange(ref Int32,Int32,Int32)//1与3是否相等,相等则替换2,返回1的原始值

Decrement(ref Int32)//递减并返回 调用add

Exchange(ref Int32,Int32)//将2设置到1并返回

Increment(ref Int32)//自增 调用add

就随便拿其中一个方法Add(ref int,int)来说(Increment和Decrement这两个方法实际上内部调用了Add方法),它会先读到第一个参数的值,在与第二个参数求和后,把结果写到给第一参数中。首先这整个过程是一个原子操作,在这个操作里面既包含了读,也包含了写。至于如何保证这个操作的原子性,估计需要查看Rotor源码才行。在代码优化方面来说,它确保了所有写操作都在Interlocked之前去执行,这保证了Interlocked里面用到的值是最新的;而任何变量的读取都在Interlocked之后读取,这保证了后面用到的值都是最新更改过的。

CompareExchange方法相当重要,虽然Interlocked提供的方法甚少,但基于这个可以扩展出其他更多方法,下面就是个例子,求出两个值的最大值,直接抄了Jeffrey的源码

查看上面代码,在进入循环之前先声明每次循环开始时target的值,在求出最值之后,核对一下target的值是否有变化,如果有变化则需要再记录新值,按照新值来再求一次最值,直到target不变为止,这就满足了Interlocked中所说的,写都在Interlocked之前发生,Interlocked往后就能读到最新的值。

   

基元内核模式

内核模式则是靠操作系统的内核对象来处理线程的同步问题。先说其弊端,它的速度会相对慢。原因有两个,其一由于它是由操作系统内核对象来实现的,需要操作系统内部去协调,另外一个原因是内核对象都是一些非托管对象,在了解了AppDomain之后就会知道,访问的对象不在当前AppDomain中的要么就进行按值封送,要么就进行按引用封送。经过观察这部分的非托管资源是按引用封送,这就会存在性能影响。综合上面两方面的两点得出内核模式的弊端。但是他也是有利的方面:1.线程在等待资源的时候不会"自旋"而是阻塞,这个节省了CPU时间,并且这个阻塞可以设定一个超时值。2.可以实现Window线程和CLR线程的同步,也可同步不同进程中的线程(前者未体验到,而对于后者则知道semaphores中有边界值资源)。3.可应用安全性设置,为经授权账户禁止访问(这个不知道是咋回事)。

内核模式的所有对象的基类是WaitHandle。内核模式的所有类层次如下

WaitHandle

EventWaitHandle

AutoResetEvent

ManualResetEvent

Semaphore

Mutex

   

WaitHandle继承MarshalByRefObject,这个就是按引用封送了非托管对象。WaitHandle里面主要是各种Wait方法,调用了Wait方法在没有收到信号之前会被阻塞。WaitOne则是等待一个信号,WaitAny(WaitHandle[] waitHandles)则是收到任意一个waitHandles的信号,WaitAll(WaitHandle[] waitHandles)则是等待所有waitHandles的信号。这些方法都有一个版本允许设置一个超时时间。其他的内核模式构造都有类似的Wait方法。

EventWaitHandle的内部维护着一个布尔值,而Wait方法会在这个布尔值为false时线程就会被阻塞,直到该布尔值为true时线程才被释放。操纵这个布尔值的方法有Set()和Reset(),前者是把布尔值设成true;后者则设成false。这相当于一个开关,调用了Reset之后线程执行到Wait就暂停了,直到Set才恢复。它有两个子类,使用的方式类似,区别在于AutoResetEvent调用Set之后自动调用Reset,使得开关马上恢复关闭状态;而ManualResetEvent就需要手动调用Set让开关关闭。这样就达到一个效果一般情况下AutoResetEvent每次释放的时候能让一条线程通过;而ManualResetEvent在手动调用Reset之前有可能会让多条线程通过。

Semaphore的内部是维护着一个整形,当构造一个Semaphore对象时会指定最大的信号量与初始信号量值,每当调用一次WaitOne,信号量就会加1,当加到最大值时,线程就会被阻塞,当调用Release的时候就会释放一个或多个信号量,此时被阻塞掉的一个或多个线程就会被释放。这个就符合生产者与消费者问题了,当生产者不断往产品队列中加入产品时,他就会WaitOne,当队列满了,就相当于信号量满了,生成者就会被阻塞,当消费者消费掉一个商品时,就会Release释放掉产品队列中的一个空间,此时因没有空间存放产品的生产者又可以开始工作往产品队列中存放产品了。

Mutex的内部与规则相对前面两者稍微复杂一点,先说与前面相似的地方就是同样都会通过WaitOne来阻塞当前线程,通过ReleastMutex来释放对线程的阻塞。区别在于WaitOne的允许第一个调用的线程通过,其余后面的线程调用到WaitOne就会被阻塞,通过了WaitOne的线程可以重复调用WaitOne多次,但是必须调用同样次数的ReleaseMutex来释放,否则会因为次数不对等导致别的线程一直处于阻塞的状态。相比起之前的几个构造,这个构造会有线程所有权与递归这两个概念,这个是单纯靠前面的构造都无法实现的,额外封装除外。

   

混合构造

上面的基元构造是用了最简单的实现方式,用户 模式有用户模式的快,但是它会带来CPU时间的浪费;内核模式解决了这个问题,但是会带来性能上的损失,各有利弊,而混合构造则是集合了两者的利,它会在内部通过一定策略适当的时机使用用户模式,再另一种情况下又会使用内核模式。但是这些层层判断带来的是内存上的开销。在多线程同步中没有完美的构造,各个构造都有利弊,存在即有意义,结合具体的应用场景就会有最优的构造可供使用。只是在于我们能否按照具体的场景权衡利弊而已。

各种Slim后缀的类,在System.Threading命名空间中,可以看到若干个以Slim后缀结尾的类:ManualResetEventSlim,SemaphoreSlim,ReaderWriterLockSlim。除了最后一个,其余两个都是在基元内核模式中有一样的构造,但是这三个类都是原有构造的简化版,尤其是前两个,使用方式跟原有的一样,但是尽量避免使用操作系统的内核对象,而达到了轻量级的效果。比如在SemaphoreSlim中使用了内核构造ManualResetEvent,但是这个构造是通过延时初始化,没达到非不得已时都不使用。至于ReaderWriterLockSlim则在后面再介绍。

Monitor与lock,lock关键字可谓是最广为人知的一种实现多线程同步的手段,那么下面则又从一段代码说起

这个方法相当简单且无实际意义,它只是为了看编译器把这段代码编译成什么样子,通过查看IL如下

留意到IL代码中出现了try…finally语句块、Monitor.Enter与Monotor.Exit方法。然后把代码更改一下再编译看看IL

IL代码

代码比较相似,但并非等价,实际上与lock语句块等价的代码如下

那么既然lock本质上是调用了Monitor,那Monitor是如何通过对一个对象加锁,然后实现线程同步。原来每个在托管堆里面的对象都有两个固定的成员,一个指向该对象类型的指针,另一个是指向一个线程同步块索引。这个索引指向一个同步块数组的元素,Monitor对线程加锁就是靠这个同步块。按照Jeffrey(CLR via C#的作者)的说法同步块中有三个字段,所有权的线程Id,等待线程的数量,递归的次数。然而我通过另一批文章了解到线程同步块的成员并非单纯这几个,有兴趣的同学可以去阅读《揭示同步块索引》的文章,有两篇。 当Monitor需要为某个对象obj加锁时,它会检查obj的同步块索引有否为数组的某个索引,如果是-1的,则从数组中找出一个空闲的同步块与之关联,同时同步块的所有权线程Id就记录下当前线程的Id;当再次有线程调用Monitor的时候就会检查同步块的所有权Id和当前线程Id是否对应上,能对应上的就让其通过,在递归次数上加1,如果对应不上的就把该线程扔到一个就绪队列(这个队列实际上也是存在同步块里面)中,并将其阻塞;这个同步块会在调用Exit的时候检查递归次数确保递归完了就清除所有权线程Id。通过等待线程数量得知是否有线程在等待,如果有则从等待队列中取出线程并释放,否则就解除与同步块的关联,让同步块等待被下个被加锁的对象使用。

Monitor中还有一对方法Wait与Pulse。前者可以使得获得到锁的线程短暂地将锁释放,而当前线程就会被阻塞而放入等待队列中。直到其他线程调用了Pulse方法,才会从等待队列中把线程放到就绪队列中,等待下次锁被释放时,才有机会被再次获取锁,具体能否获取就要看等待队列中的情况了。

ReaderWriterLock读写锁,传统的lock关键字(即等价于Monitor的Enter和Exit),他对共享资源的锁是全互斥锁,一经加锁的资源其他资源完全不能访问。

ReaderWriterLock对互斥资源的加的锁分读锁与写锁,类似于数据库中提到的共享锁和排他锁。大致情况是加了读锁的资源允许多个线程对其访问,而加了写锁的资源只有一个线程可以对其访问。两种加了不同缩的线程都不能同时访问资源,而严格来说,加了读锁的线程只要在同一个队列中的都能访问资源,而不同队列的则不能访问;加了写锁的资源只能在一个队列中,而写锁队列中只有一个线程能访问资源。区分读锁的线程是否在于统一个队列中的判断标准是,本次加读锁的线程与上次加读锁的线程这个时间段中,有否别的线程加了写锁,没没别的线程加写锁,则这两个线程都在同一个读锁队列中。

ReaderWriterLockSlimReaderWriterLock类似,是后者的升级版,出现在.NET Framework3.5,据说是优化了递归和简化了操作。在此递归策略我尚未深究过。目前大概列举一下它们通常用的方法

ReaderWriterLock常用的方法

Acqurie或Release ReaderLock或WriteLock 的排列组合

UpGradeToWriteLock/DownGradeFromWriteLock 用于在读锁中升级到写锁。当然在这个升级的过程中也涉及到线程从读锁队列切换到写锁队列中,因此需要等待。

ReleaseLock/RestoreLock 释放所有锁和恢复锁状态

   

ReaderWriterLock实现IDispose接口,其方法则是以下模式

TryEnter/Enter/Exit ReadLock/WriteLock/UpGradeableReadLock

(以上内容引用自另一篇笔记《ReaderWriterLock)

CoutdownEvent比较少用的混合构造,这个跟Semaphore相反,体现在Semaphore是在内部计数(也就是信号量)达到最大值的时候让线程阻塞,而CountdownEvent是在内部计数达到0的时候才让线程阻塞。其方法有

AddCount //计数递增;

Signal //计数递减;

Reset //计数重设为指定或初始;

Wait //当且仅当计数为0才不阻塞,否则就阻塞。

Barrier也是一个比较少用的混合构造,用于处理多线程在分步骤的操作中协作问题。它内部维护着一个计数,该计数代表这次协作的参与者数量,当不同的线程调用SignalAndWait的时候会给这个计数加1并且把调用的线程阻塞,直到计数达到最大值的时候,才会释放所有被阻塞的线程。假设还是不明白的话就看一下MSND上面的示例代码

这里给Barrier初始化的参与者数量是3,同时每完成一个步骤的时候会调用委托,该方法是输出count的值步骤索引。参与者数量后来增加了两个又减少了一个。每个参与者的操作都是相同,给count进行原子自增,自增完则调用SgnalAndWait告知Barrier当前步骤已完成并等待下一个步骤的开始。但是第三次由于回调方法里抛出了一个异常,每个参与者在调用SignalAndWait的时候都会抛出一个异常。通过Parallel开始了一个并行操作。假设并行开的作业数跟Barrier参与者数量不一样就会导致在SignalAndWait会有非预期的情况出现。

接下来说两个Attribute,这个估计不算是同步构造,但是也能在线程同步中发挥作用

MethodImplAttribute这个Attribute适用于方法的,当给定的参数是MethodImplOptions.Synchronized,它会对整个方法的方法体进行加锁,凡是调用这个方法的线程在没有获得锁的时候就会被阻塞,直到拥有锁的线程释放了才将其唤醒。对静态方法而言它就相当于把该类的类型对象给锁了,即lock(typeof(ClassType));对于实例方法他就相当于把该对象的实例给锁了,即lock(this)。最开始对它内部调用了lock这个结论存在猜疑,于是用IL编译了一下,发现方法体的代码没啥异样,查看了一些源码也好无头绪,后来发现它的IL方法头跟普通的方法有区别,多了一个synchronized

于是网上找各种资料,最后发现"junchu25"的博客[1][2]里提到用WinDbg来查看JIT生成的代码。

调用Attribute的

调用lock的

对于用这个Attribute实现的线程同步连Jeffrey都不推荐使用。

System.Runtime.Remoting.Contexts.SynchronizationAttribute这个Attribute适用于类,在类的定义中加了这个Attribute并继承与ContextBoundOject的类,它会对类中的所有方法都加上同一个锁,对比MethodImplAttribute它的范围更广,当一个线程调用此类的任何方法时,如果没有获得锁,那么该线程就会被阻塞。有个说法是它本质上调用了lock,对于这个说法的求证就更不容易,国内的资源少之又少,里面又涉及到AppDomain,线程上下文,最后核心的就是由SynchronizedServerContextSink这个类去实现的。AppDomain应该要另立篇进行介绍。但是在这里也要稍微说一下,以前以为内存中就是有线程栈与堆内存,而这只是很基本的划分,堆内存还会划分成若干个AppDomain,在每个AppDomain中也至少有一个上下文,每个对象都会从属与一个AppDomain里面的一个上下文中。跨AppDomain的对象是不能直接访问的,要么进行按值封送(相当于深复制一个对象到调用的AppDomain),要么就按引用封送。对于按引用封送则需要该类继承MarshalByRefObject。对继承了这个类的对象进行调用时都不是调用类的本身,而是通过代理的形式进行调用。那么跨上下文的也需要进行按值封送操作。平常构造的一个对象都是在进程默认AppDomain下的默认上下文中,而使用了SynchronizationAttribute特性的类它的实例是属于另外的一个上下文中,继承了ContextBoundObject基类的类进行跨上下文访问对象时也是通过按引用封送的方式用代理访问对象,并非访问到对象本身。至于是否跨上下文访问对象可以通过的RemotingServices.IsObjectOutOfContext(obj)方法进行判断。SynchronizedServerContextSink是mscorlib的一个内部类。当线程调用跨上下文的对象时,这个调用会被SynchronizedServerContextSink封装成WorkItem的对象,该对象也mscorlib的中的一个内部类,SynchronizedServerContextSink就请求SynchronizationAttribute,Attribute根据现在是否有多个WorkItem的执行请求来决定当前处理的这个WorkItem会马上执行还是放到一个先进先出的WorkItem队列中按顺序执行,这个队列是SynchronizationAttribute的一个成员,队列成员入队出队时或者Attribute判断是否马上执行WorkItem时都需要获取一个lock的锁,被锁的对象也正是这个WorkItem的队列。这里面涉及到几个类的交互,鄙人现在还没完全看清,以上这个处理过程可能有错,待分析清楚再进行补充。不过通过这个Attribute实现的线程同步按鄙人的直觉也是不推荐使用的,主要是性能方面的损耗,锁的范围也比较大。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程之线程同步 的相关文章

  • C# AES Rijndael - 检测无效密码

    我正在使用 Rijndael 加密程序中的一些敏感数据 当用户输入错误的密码时 大多数情况下CryptographicException抛出消息 填充无效且无法删除 然而 CryptStream 不会抛出密码错误的异常 而是返回错误解密的流
  • 无法在更新面板中找到上传的文件

    aspx
  • 对相当大的整数的大集合的操作的快速实现

    描述 我实现了以下类 LabSetInt64 参见下面的代码 这里的目标是尽可能快地操作大量大整数 最多 10M 的值 我的主要要求集中在 至关重要 尽快获取集合的大小 基数 重要 能够非常快速地迭代一组集合 所以 从下面的实现开始 我还有
  • 函数原型和数组参数

    我正在学习 C 语法 并且已经开始研究数组了 我想问你一个问题 但首先让我回顾一下 这样我就知道我已经弄清楚了 我知道您可以使用以下语法将变量定义为数组 name
  • CMake - 未定义参考

    我正在尝试将 gtest 包含到我的项目中 问题是我在 GTest 中收到未定义的引用错误 我正在尝试在 Gtest 中测试 Node 类 在节点的构造函数中 我使用类记录器 尽管我已将库记录器添加到 gtest target 中 但我仍然
  • 为什么测试在 TeamCity 中运行比直接在 NUnit 中运行需要更长的时间?

    我进行了一些 C 性能测试 基本上运行两种不同的方法 并检查一种方法的运行速度是否比另一种方法快得多 当我在 NUnit 本地运行它们时 其中一个测试的运行速度是另一个测试的十倍 因此我有一个 NUnit 测试 它使用Stopwatch检查
  • 使用成员函数作为 std::shared_ptr 的自定义删除器时出现问题

    我正在尝试弄清楚如何将 std shared ptr 与自定义删除器一起使用 具体来说 我将其与 SDL Surface 一起使用 如下所示 std shared ptr
  • 编译器在函数名称前添加下划线前缀的原因是什么?

    当我看到 C 应用程序的汇编代码时 如下所示 emacs hello c clang S O hello c o hello s cat hello s 函数名称以下划线作为前缀 例如callq printf 为什么这样做以及它有什么优点
  • 在标准库中静态链接时如何支持动态插件?

    假设一个应用程序myapp exe是使用构建的g 它使用标志 static libstdc 这样就可以安装在没有环境的情况下libstdc so myapp exe还添加了对某些功能的插件支持plugf可以通过动态加载dlopen来自共享库
  • 使用 for 循环创建链表

    这是我的结构 struct ListItem int data struct ListItem next 假设链表的第一个节点的 data 0 我想编写一个 for 循环来创建大小为 5 的链表 但我不知道如何工作 我尝试了以下方法 int
  • 如何忽略搜索条件中的空属性

    我有一个不好的要求要做 无论如何 我必须在我的应用程序中实现它 我有一个Track class public class Track public string Name get set public string City get set
  • DLL 中的 XP 风格组合框

    我需要使用 C 和 WIN32 API 无 MFC 在 DLL 中创建 XP 风格的组合框 我设法在 DLL 中创建控件 不是以 XP 风格 我设法在带有清单的 exe 中创建 XP 样式组合框 但它在 DLL 中不起作用 为了让您的 DL
  • C语言:如何获取使用strtok()一次后剩余的字符串

    我的字符串是 A B C D E 分隔符是 如何获取执行 strtok 一次后剩余的字符串 即 B C D E char a A B C D E char separator char b strtok a separator printf
  • 在发送传出请求之前将新的 SoapClient 绑定到特定 IP 地址

    假设应用程序所在的计算机具有 SoapClient 具体来说 我正在使用 Microsoft Web Service3 Messaging SoapClient 它通过发送传出请求并获取 SoapEnvelope 作为回报 完善的流程 与远
  • 派生类的聚合初始化

    以下代码无法使用 Visual Studio2017 或在线 GDB 进行编译 我期望它能够编译 因为迭代器只是一个具有类型的类 并且它是从公共继承的 这是不允许的还是在 VS2017 中不起作用 template
  • 序列化时如何跳过 xml 声明?

    我正在尝试输出一个没有 xml 头的 xml 文件 例如 我试过 Type t obj GetType XmlSerializer xs new XmlSerializer t XmlWriter xw XmlWriter Create c
  • C# 记录类型:记录子类之间的相等比较

    给定父记录类型 public record Foo string Value 和两个记录子类Bar and Bee我想知道是否可以实施Equals在基类中 因此 Foo Bar 或 Bee 的实例都被考虑equal基于Value 两者都与E
  • 没有运算符“<<”与这些操作数匹配[重复]

    这个问题在这里已经有答案了 不知道发生了什么事 我查看了与此问题类似的其他帖子 但到目前为止没有解决方案有帮助 这是带有错误部分注释的代码 在某一时刻 它说 不起作用 而在代码的其余部分中 它说 include
  • 如何通过Task.ContinueWith创建传递?

    我想在原始任务结束时添加一个任务 但想保留原始结果和类型 附加任务仅用于记录目的 例如写入控制台等 例如 Task Run gt DateTime Now Hour gt 12 Hey throw new Exception Continu
  • 如何使用字符串的值将字符串转换为 wstring?

    我是 C 新手 我有这个问题 我有一个名为 DATA DIR 的字符串 需要将其格式化为 wstring string str DATA DIR std wstring temp L s str Visual Studio 告诉我没有与参数

随机推荐

  • 数仓建模过程——写指标

    1 维度 描述信息 事实 度量值 比如 我早上花了5元买早餐 其中时间地点买了什么等就是描述信息就是维度 具体的金额数字就是事实 2 ods层一般就是原始数据 比如用户行为日志 导入到hdfs中是一条条日志 那么日志的ods层表结构就只有s
  • 特征筛选9——根据重要性SelectFromModel筛选特征(有监督筛选)

    策略思想 使用能够进行特征重要性评估的模型 一般带有feature importances或coef 参数 训练特征 如果结果重要性的得分小于阈值 就会被认为是不重要的特征比如小于0 1 mean 重要性 示例代码 import panda
  • Windows Server 2012 R2 -网站—安全性设置-身份验证(VMware workstation环境)

    安装身份验证组件 匿名身份验证 任何用户都可以直接匿名连接此网站 不需要身份认证 基本身份验证 要求用户输入用户名及密码 但是用户名及密码并没有加密 容易被拦截获取数据 默认域 用户连接网站时 可以使用两种方式 1 域用户账户 用户输入的用
  • JAVA 练习题(2)

    从键盘输入8个整数存放在一个数组中 然后将奇数和偶数分别存入到两个不同的数组中 并按奇数 偶数交替的顺序输出这两个数组中的所有数据 先交替输出 如果奇数个数多 则再输出剩下的奇数 如果偶数个数多 则再输出剩下的偶数 提示与要求 1 定义一个
  • 动手学区块链学习笔记(一):加密算法介绍

    引言 本文根据实验楼以及自己查询到的一些资料 文末给出 模拟了一下区块链从诞生到交易的整个过程 也算是弥补了一下之前区块链的一些缺失知识 哈希加密原理介绍 什么是比特币 比特币是一种加密货币 也是一种分布式数字货币 它的创建者使用匿名身份被
  • [VS] 诊断工具,CPU调优

    工具 vs2019 系统 win10 语言 C github 调试 demo dangwei 90 ProcessOptimize github com 本文主要通过 VS 自带的诊断工具 对程序进行CPU调优 Begin 1 编译测试 d
  • Acwing 1227. 分巧克力

    每个巧克力能切多少块是可以计算出来的 假设当前巧克力的边长是Wi Hi的话 若要切出边长是x的巧克力的话 能切的块数为 Wi x Hi x int默认下取整 对于每一块巧克力来说 切出来的数量随着边长的增加而递减的 我们要找到一个块数满足
  • 利用遗传算法GA和粒子群算法PSO优化算法,将BP神经网络训练集的MSE作为适应度函数

    利用遗传算法GA和粒子群算法PSO优化算法 将BP神经网络训练集的MSE作为适应度函数 获取最优的权值和阈值在反向输入到BP神经网络里构建回归预测模型 同时能够打印出模型的多个评价指标 具体效果可以看图 ID 325066919444354
  • Pycharm在导入虚拟环境变量的时候报错:Please specify a different SDK name解决方法

    发生的错误的远影是因为虚拟环境的命名有重复 是因为你重复导入同一个虚拟环境所致 解决方法 在设置的Project Interpreter中选择你所需要的虚拟环境 在下拉框中选择Show All 然后你就可以看到你的所有虚拟环境 点击 删除名
  • 初级项目——记账系统、双色球

    一 记账系统 主功能 展示收支明细 登记收入 登记支出 退出系统 package com wfl test import java util Scanner author wfl Version 1 0 date 2022 5 17 17
  • Linux网络-数据链路层,MAC帧解析,ARP协议

    目录 数据链路层VS网络层 以太网概念 以太网的帧格式 报文格式 也可以称之为MAC帧 MAC地址的概念 MAC帧格式 局域网通信原理 MTU MTU说明 MTU对IP协议的影响 MTU对UDP协议的影响 MTU对TCP协议的影响 ARP协
  • 使窗口只第一次访问时弹出

    有时为了某种需要 要求进入一个页面时弹出一个窗口 但每次进入时都会弹出 就使人感受到烦 怎样实只弹出一次呢 下面的程序将实现浏览才第一次访问这页时弹出窗口 以后再进入则不会弹出 在 之间加入下列代码
  • ILRuntime来实现热更新的优与劣!

    热更新 Q1 使用ILRuntime来实现热更新的可行度有多高 大家有没有使用经验分享 一般热更新分两块 代码 资源 资源热更几乎都是通过AssetBundle来搞 代码热更可以用某种解释器 解释执行的语言来搞 可供选择的有Lua as3
  • 发现一个hibernate针对derby数据库的问题bug,及解决办法

    最近apache软件基金会 发布了derby10 7 由于derby到10 7版本才支持boolean数据类型 今天发现一个hibernate针对derby数据库的一个bug 特意把它贴出来 以提醒他人 2011 01 01发现的hiber
  • qwt之获取动态变化x轴和y轴坐标的最大值和最小值

    一 新建工程 将qwt基本画图功能配置完毕 开始进行页面布局 如图所示 二 在值变化按钮中 可以实现每次进行点击x轴的时候 都会获取动态的x轴的最大值和最小值 进行点击转到槽之后x轴部分加入以下代码 void Widget on pushB
  • Basic Level 1093 字符串A+B (20分)

    题目 给定两个字符串 A 和 B 本题要求你输出 A B 即两个字符串的并集 要求先输出 A 再输出 B 但重复的字符必须被剔除 输入格式 输入在两行中分别给出 A 和 B 均为长度不超过 1 0 6 10 6 106的 由可见 ASCII
  • 利用DDA、中点画线和Bresenham法画直线

    一 实验目的 在空白图像上绘制两个点 坐标分别为 24 26 和 140 624 并且绘制出以该点为端点的直线 要求利用DDA 中点直线及Bresenham算法进行绘制 二 设计方案 使用DDA 中点画线和Bresenham画线法绘制直线
  • VideoCapture.get()(python)

    param define cv2 VideoCapture get 0 视频文件的当前位置 播放 以毫秒为单位 cv2 VideoCapture get 1 基于以0开始的被捕获或解码的帧索引 cv2 VideoCapture get 2
  • 机器学习分类算法(六)-随机森林算法

    集成算法 集成学习 ensemble learning 是目前非常流行的机器学习策略 基本上所有问题都可以借用其思想来得到效果上的提升 基本出发点就是把算法和各种策略集中在一起 说白了就是一个搞不定大家一起上 集成学习既可以用于分类问题 也
  • 多线程之线程同步

    多线程内容大致分两部分 其一是异步操作 可通过专用 线程池 Task Parallel PLINQ等 而这里又涉及工作线程与IO线程 其二是线程同步问题 鄙人现在学习与探究的是线程同步问题 通过学习 CLR via C 里面的内容 对线程同