我要投搞

标签云

收藏小站

爱尚经典语录、名言、句子、散文、日志、唯美图片

当前位置:港彩神鹰 > 读入原语 >

并发操作: Windows Vista 新增的同步原语

归档日期:04-30       文本归类:读入原语      文章编辑:爱尚语录

  条件变量在其他线程库中已经存在一段时间了,只是很遗憾地被 Windows SDK 遗漏了。条件变量主要用来根据一些条件测试的结果同步化一组线程。尽管可通过使用现有同步结构组合来做到这一点,但是条件变量具有释放已获取的锁,并通过一个原子操作进入休眠状态的能力。它还提供了一种更清楚且又少出错的方法,以用于实现所需的行为。

  Windows SDK for Windows Vista(可供下载)公开了 CONDITION_VARIABLE 结构的条件变量。您可用 InitializeConditionVariable 函数创建该结构。没有用来清理或销毁 CONDITION_VARIABLE 结构的函数,因为底层实现用不着它。

  通过使用函数 SleepConditionVariableCS(使用关键节时)或 SleepConditionVariableSRW(使用 Slim 读取器锁/写入器锁时),您可让线程等待条件变量。当另一条线程调用 WakeConditionVariable 或 WakeAllConditionVariable 时,这些休眠线程将被释放,这取决于调用线程是想要释放等待条件变量的一条线程还是所有线程。

  常见的生产者/使用者问题代表了可使用条件变量的情况。这一典型示例是指生产者生成数据并将其置入缓冲区,而使用者则从缓冲区抓取待处理的数据片段的情形。该问题指出了一种需求,即保证生产者不会试图向填满的缓冲区添加数据,而使用者不会试图从空缓冲区抓取数据。我们将分析本情形,以向您说明条件变量是如何帮助解决问题的。

  针对此例,我们将创建一个向共享队列传送数值数据的单一生产者线程。我们然后创建五个使用者线程。每个使用者线程将从队列中转移一个项目并进行处理。当处理完当前数据段后,使用者线程将循环,无限重复该过程。

  在早期版本的 Windows 中,可用 Win32 事件和关键节组合来解决生产者/使用者问题。当资源可供使用者使用时,关键节会保护共享资源、避免出现并发性访问和事件信号。

  在我们首次尝试解决这个问题时,我们将标准模板库 (Standard Template Library, STL) 整数列表用作共享资源。由于列表会动态扩展,我们无需使用事件以信号形式通知列表何时是未填满的,我们只需了解它何时不是空的,这样使用者就能知道其中有内容可供使用。(如果您打算使用固定大小的数组来容纳共享队列,则需要一个未满事件,以确保您不会向缓冲区写入过多内容。)我们随后声明并初始化 CRITICAL_SECTION 对象以及用于说明列表何时不为空的自动重置事件。

  所示的生产者线程将首先尝试获取关键节,并且如果获取成功,将随后在共享列表的末尾插入一个整数值。该线程然后释放关键节并设置非空事件。因为我们在使用一个自动重置事件,因此只释放一个等待本事件的线

  所示的使用者线程将查看队列是否为空。如果队列不为空,该线程将转移一个项目并释放关键节。如果队列为空,使用者线程将返回休眠状态,继续等待非空事件。在第一个使用者线程忙于处理它从队列转移的项目时,生产者将唤醒另一个使用者线程拾取下一段工作以确保队列处于移动状态。

  尽管本解决方案通常是可行的,但对本实现也有一些局限。比如说,正确初始化同步对象就是个难题。例如,我们必须决定在将数据推入列表时,生产者应该只唤醒一个使用者还是唤醒所有使用者。这可通过我们初始化事件的方法来控制。如果我们使用自动重置事件,就只能释放一个线程。但如果我们想要唤醒所有线程,我们将使用手动重置事件,并且必须记住在正确的时间调用 ResetEvent。

  必须确保当队列为空时,使用者线程在等待非空事件之前释放关键节。我们还必须确保不要错误使用生产者线程中的 PulseEvent 来发送非空事件信号,因为这样会导致争用状况。如果在使用者线程刚刚释放关键节、尚未调用 WaitForSingleObject 时被抢占,并且生产者随即调用 PulseEvent,就会出现这个问题。PulseEvent 并非“粘滞的”(sticky),它将只释放目前正在等待事件的那些线程。当被抢占的线程恢复时,将不会发送事件信号,并且将丢失唤醒。

  使用条件变量将更容易获得正确的解决方案。本方法仍然使用关键节,但它用条件变量取代非空事件。我们通过调用指向我们的 CONDITION_VARIABLE 结构的 InitializeConditionVariable,来初始化主函数内的条件变量。

  所示使用者线程进入关键节并查看队列是否为空。如果为空,则调用 SleepConditionVariableCS。此函数会释放关键节,并通过一个原子操作让此线程进入休眠状态,从而避免在两个任务间隙使用者被抢占时可能出现的争用情况。SleepConditionVariableCS 函数还接受一个超时参数,从而让我们在不想等得太久时,可以做别的工作。

  当得到条件变量信号时,该线程醒来并在 SleepConditionVariableCS 函数返回之前再次自动锁定关键节。使用者线程然后往回循环,并再次查看队列是否为空。当线程被释放后,再次检测状态非常重要,因为其状态可能在使用者被释放之前已被另一个线程更改;另外,条件变量还可能被骗醒 (spurious wakeup),这样它可能会在条件发生变化之前运行。我们随后从队列中转移工作项并释放关键节。

  所示生产者线程通过进入关键节启动。一旦有了锁,生产者线程就会将一个新工作项推入队列,然后释放关键节。现在它可通过调用 WakeConditionVariable 释放使用者线程。此函数仅释放一个线程,这与不使用条件变量的解决方案一样。如果我们想释放所有使用者线程,我们将调用 WakeAllConditionVariable 函数。条件变量语法可以清楚显示出发送线程的过程,因为函数名称说明了将要做的事情。使用事件会使这一过程更容易出错,原因是所需行为并不是用事件信号函数而是初始化事件的时间来指定的。

  如果生产者线程在它刚刚释放关键节但尚未调用 Wake 函数之前被抢占,就为另一个线程在使用者线程被释放之前修改队列状态提供了缺口。这正是我们说的释放条件时,使用者线程必须对其进行再次检查的原因。

  条件变量在某些情况中也可能更高效。SleepConditionVariableCS 和 SleepConditionVariableSRW API 会尽可能候试图避免进入内核模式的行程。但不使用条件变量的示例在调用 WaitForSingleObject 时,总是会发生至少一次内核模式往返。

  这样,与自己开发的实现 (homegrown implementation) 相比,本机条件变量实现可带来诸多好处,并且不会降低灵活性。生成的代码更简单、更容易阅读,并且性能也可提高。这大大降低了导致许多难以发现的细微错误的可能性。

  读取器锁/写入器锁用于保护您想允许多个读取器并发访问,但在更新时只允许写入器访问的一段数据。通常,这些锁最适用于需要频繁读取和更新数据的情形。恰当使用读取器锁/写入器锁有利于增加可伸缩性。

  读取器锁/写入器锁出现已有一段时间了。但在 Windows Vista 发布之前,如果您在 Windows 中编写本机用户模式代码并且需要读取器锁/写入器锁,您唯一的选择是编写自己的锁或修改教科书上的实现。Windows Vista 包含了一种称为 Slim 读取器锁/写入器锁 (SRW) 的读取器锁/写入器锁。让我们来看看这个新同步原语的功能集,测试一下可用来使用它的 API,并与用 Win32

  SRW 锁的设计初衷是快速、小型(如其名),同时仍能保持使用时的资源效率。它以 Windows 内核键控事件机制为基础生成,引入此机制是为了解决应用程序使用大量 CRITICAL_SECTION 对象时可能发生的资源匮乏问题。有些读取器锁/写入器锁旨在使读取器优先于写入器,反之亦然。但 SRW 锁旨在不偏向任何一方。也就是说,如果您的应用程序要求数据更新的优先级高于数据读取,则最好考虑选用偏向写入器的另一种读取器锁/写入器锁。但在编写自己的锁之前,您最好先试试 SRW 锁,看看它在您的应用程序环境中表现如何。

  读取器锁/写入器锁有时支持的其他两项功能分别是递归获得锁,以及升级(或降级)线程被授予的锁访问权。首先,关于递归获得:如果您为应用程序设计的锁策略要求递归获得同步目标,很可能出现一个红色标志,提示您重新检查锁策略以清除递归。这是我们的看法,是由于多次执行锁获得和释放代码所带来的额外系统开销而造成的,或许还有一个更重要的原因,那就是确保锁释放和锁获得之间的平衡通常是很难证明是正确的。

  SRW 锁不支持递归获得。此支持会造成额外系统开销,原因是为了维持准确性需进行逐线程的计数。SRW 锁也不支持从共享访问升级到独占访问,反之也不支持从独占访问降级(较少见)到共享访问。支持升级能力可能会造成难以接受的复杂性和额外系统开销,这种开销甚至会影响锁内共享和独占获得代码的常见情况。它还要求定义关于如何选择等待中的读取器、等待中的写入器和等待升级的读取器的策略,这又将与无偏向的基本设计目标相抵触。

  使用 Slim 读取器锁/写入器锁的第一步是声明 SRWLOCK 结构并用 InitializeSRWLock 进行初始化:

  VOID WINAPI InitializeSRWLock(PSRWLOCK SRWLock);

  SRW 锁摆脱了每个对象均有一个初始化和清理函数的 Win32 惯例模式。当您结束使用 SRW 锁时,无需调用清理函数。

  VOID WINAPI AcquireSRWLockExclusive(PSRWLOCK SRWLock); VOID WINAPI ReleaseSRWLockExclusive(PSRWLOCK SRWLock); VOID WINAPI AcquireSRWLockShared(PSRWLOCK SRWLock); VOID WINAPI ReleaseSRWLockShared(PSRWLOCK SRWLock);

  顾名思义,AcquireSRWLockExclusive 函数用于获得供调用方独占访问的锁。一旦被授予独占访问权,所有要求任一访问类型的其他线程均将被阻止,直到用 ReleaseSRWLockExclusive 互补函数释放该锁。相对的,AcquireSRWLockShared 会以共享访问权获得该锁。此时,如果锁是无主的或者已被其他具有共享访问权的线程获得,同样要求共享访问的其他线程则无需等待。

  Slim 读取器锁/写入器锁可与使用 SleepConditionVariableSRW 函数的条件变量结合使用:

  BOOL WINAPI SleepConditionVariableSRW( PCONDITION_VARIABLE ConditionVariable, PSRWLOCK SRWLock, DWORD dwMilliseconds, ULONG Flags );

  SleepConditionVariableSRW 释放 SRW 锁,并作为原子操作等待特定条件变量。在发送条件变量信号或 dwMilliseconds 内指定的超时结束之前,此函数不会返回。如果 dwMilliseconds 为 INFINITE,该函数将永不超时。如果 Flags 参数指定 CONDITION_VARIABLE_LOCKMODE_SHARED,此函数认为 SRW 锁具有共享访问权;否则将认为是独占访问,并在成功返回后,该锁将以指定的访问权被重新获得。

  在试验这些 API 的过程中,我们发现了一些编码时需格外注意的问题。请注意,没有任何锁获得或释放 API 被定义为返回结果的类型。如果 SRW 锁当前没有为一个线程所有,并发生了对 ReleaseSRWLockShared 或 ReleaseSRWLockExclusive 的调用,将会引发一个 STATUS_RESOURCE_NOT_OWNED 结构的异常。这并非坏事,因为开发人员可以明显地看出错误。

  我们以前提到,不支持递归锁获得。再次尝试递归获得独占访问权的结果是,对 AcquireSRWExclusive 的第二次调用将永不返回,原因是它将自动发生死锁,使线程被阻止在内部,只有等待锁被释放。您可能需要附加一个调试器到此过程中,以查看具体情形并了解到底发生了什么问题。如果另一个线程已经尝试在两次 AcquireSRWShared 调用间隙以独占方式获得锁,那么从已获得共享访问权的线程调用 AcquireSRWShared 也将导致死锁。

  在确保使用正确的获得与释放函数对时也要格外注意 — 错误地将 AcquireSRWLockExclusive 与 ReleaseSRWLockShared 配对(或反之亦然)不会引发任何异常。如果已引发针对这些出错情形的异常将很有帮助,但检测错误可能会产生不必要的资源或性能开销。

  本文中的示例代码包括称为 ReaderWriterExample 的程序源代码,它允许针对不同读取器锁/写入器锁实现进行试验。该程序支持的锁类型是 SRW 锁、我们实现的读取器锁/写入器锁的两个变量和关键节。自定义读取器锁/写入器锁是用 Win32 关键节生成的,以保护锁的数据、向等待中的读取器发送信号的事件和用于写入器的信号灯。两种自定义锁的区别在于,一种偏向写入器,另一种既不偏向读取器也不偏向写入器。

  所有这些新奇的测试都是在双核 Intel Xeon 3.20 GHz 处理器系统、64 位的 Windows Vista 平台上进行的。为进行这些测试,系统被配置成禁用超线 包含每个线 个迭代,其中有锁时所做的工作最少。因此,结果可反映出锁获得和释放的实际消耗。总结的结果如

  其中有一些有趣的发现。首先,在只使用一个读取器线程或一个写入器线程(结果的前两列)时,未出现锁竞争,并且 SRW 锁的性能非常接近关键节的性能。在使用四个写入器线程时,所有线程都竞争对锁的独占访问权,SRW 锁的用时仅约占关键节方法所用时间的一半。使用独占模式锁的 SRW 锁的性能似乎优于关键节,并且值得考虑用作替代品。请注意,在标有 2 个读取器/2 个写入器和 3 个读取器/1 个写入器的栏中,SRW 锁比使用关键节的锁速度快很多。这说明了允许数据读取器并行工作的读取器锁/写入器锁的优点。

  关于我们自己开发的读取器锁/写入器锁,我们该作何评价呢?与两个内置 Windows 锁相比,它们的性能似乎很差,尤其是在读取器和写入器争用的重负载下运行时。但请看看 2 个读取器/2 个写入器和 3 个读取器/1 个写入器的结果。请注意,锁策略确实会影响总体行为和性能。偏向写入器的锁速度慢于既不偏向读取器也不偏向写入器的锁。这是因为在写入器等待更新时,并行机制被迫向写入器提供优先权。当然,这么做通常是为了确保读取器能看到最新数据,因此使用偏向写入器的锁在本例中是正确的选择。

  当四个写入器竞争锁时,会出现自定义锁的另一个问题。在出现对独占访问权的许多竞争时,性能为何变得如此之差?问题在于每个线程都在竞争保护内部数据的关键节的访问权。对关键节的竞争导致进入内核模式的行程,线程在其中被置于事件休眠状态。一旦进入关键节,就会检查锁的状态,以查看任何读取器是否具有共享访问权或另一个写入器是否已经具有独占访问权。在任何一种情况下,想得到独占访问权的线程必须离开关键节,然后等待信号灯。要线程具有独占访问权并可继续运行时,将发送上述信号灯。如您可看到的,必须频繁等待多个同步目标以获得独占访问权确实降低了性能。

  ReaderWriterExample 应用程序包括多个命令行开关,这样就可定制它的行为以允许通过使用不同类型的读取器锁/写入器锁来试验不同的方案。受锁保护的数据属于简单的 LONG 类型。然而,应用程序在有锁时允许指定待完成的额外数量的工作,以便模拟访问或更新更复杂的数据结构。可为读取器和写入器单独指定额外工作参数,从而允许数据结构模拟,该结构会因为更新而不是读取而带来更多负担。还有用来指定读取器/写入器在每个锁访问间隙的工作量的参数。通过固定执行计算的循环来模拟工作。

  我们都知道,自己开发的锁在读取器和写入器频繁争用而处于重负载下的性能不是很好。另外,SRW 锁在第一个示例中的所有情况下则都运行良好。这是否意味着既然有了 SRW 锁,您便没必要再构建自己的读取器锁/写入器锁呢?不一定。我们来看看,将锁用于更接近应用程序实际应用方案时会发生什么。

  在这些方案中,读取器的共享访问次数是 1,000,000,而写入器的独占访问次数是 150,000。这与我们前面所说的相符 — 共享访问对独占访问的比例较高时,读取器锁/写入器锁才有意义。另外,读取器拥有对 2000 个工作单元的锁以模拟读取请求,而写入器则拥有 3000 个工作单元的锁以模拟升级数据所带来的额外成本。一个读取器线程在每次访问锁的间隙执行 100 个工作单元,这样访问的时间间隔较短,而写入器在尝试独占访问之前执行 10,000 个工作单元,这样更新的时间间隔就会延长。本方法可减少对锁的整体争用。

  我们使用 2 个读取器/ 2 个写入器和 3 个读取器/1 个写入器来进行本测试。总结的结果如

  所示。表中的数字是每个线程完成其工作所需的时间(以秒为单位)。对于读取器线程,第二个数字是读取器观察到的数据更新次数。

  这些结果显示我们自己开发的锁和 SRW 锁存在更多的相似性。请注意不同的策略是如何影响结果的。偏向写入器的锁会让读取器等待而允许写入器访问,这意味着更新被赋予更高的优先级并相应地允许读取器看到更多更新。在应用程序中赋予更新优先权有时非常重要,因此了解预期负载下的锁策略及其性能特征是需要考虑的重要因素。

  SRW 锁是 Windows 平台上杰出的新同步原语。Windows 首次向本机系统程序员提供内置读取器锁/写入器锁。它在许多不同的环境下均运行良好,并应成为您使用的首选读取器锁/写入器锁。您的应用程序有时可受益于不同的锁策略,但正如我们所演示的,建立适用于许多方案的读取器锁/写入器锁并不那么容易。

  在建立多线程系统时,时不时会出现如何确保正确初始化供多个线程共享的对象或资源的问题。C 和 C++ 并未提供解决这一问题的帮助,原因是语言标准未提及多线程支持。假设这样一个示例,该示例包含一个用于记录消息日志的 Logger 对象实例,其中一个要求是按需进行对象实例化,而不是在开始执行程序时创建对象。当有多个线程在 GetLogger 函数内部同时执行以访问我们系统中的 Logger 对象时,

  Logger* GetLogger() { // C++ will execute the Logger’s constructor // only the first time this method is called. static Logger logger; return }

  静态 Logger 对象的初始化实际上不止发生一次,原因是编译器并未在其结构上加入任何同步。这可能会产生损坏的 Logger 对象,当使用该对象时,即使处于最佳情形,也会产生异常 — 但不能保证错误很明显,并且系统可能运行了很长时间后才会有人发现此问题。解决此问题的一种办法是重新编写函数并引入同步功能,这样它就可利用

  现在,进入 GetLogger 函数的每个线程都将试图进入关键节,即意味着每次只允许一个线程进入受保护的代码块。在关键节执行的线程会检查 pLogger 的值,只有值为 NULL 时才会创建一个 Logger 对象的实例。此操作仅在第一个线程进入关键节后会发生一次。随后进入的其他所有线程将发现 pLogger 不是 NULL,然后会退出关键节而不再执行任何工作。到达返回语句时,pLogger 值将是非空值,并可被返回给调用方。

  线程安全初始化代码表面上看似合理的解决方案。但在此必须打个折扣,尤其是当许多线程结束时同时调用 GetLogger 函数的情况下。一旦第一个线程已经完成分配、构建和设置 pLogger 指针,实际上根本不再需要后面的线程进入关键节对象。该指针始终保持有效。这种认识带来了一个称为仔细查过锁定模式的 C++ 编程设计模式。图 7显示了使用仔细查过锁定模式的 GetLogger 实现。此模式规定 pLogger 变量要被检查两次。第一次是检查变量,在关键节之外进行。如果发现 pLogger 为 NULL,线程随后将进入关键节。一旦进入,它将在实例化和设置 pLogger 变量之前再次检查 pLogger 是否为空,原因是等待关键节时由于受另一线程的竞争,该线程可能被阻止。Figure 7 仔细查过锁定复制代码Logger* GetLogger() { volatile static Logger* pLogger = 0; if (pLogger == NULL) { EnterCriticalSection( if (pLogger == NULL) { try { pLogger = new Logger(); } catch (...) { // Something went wrong. } } LeaveCriticalSection( } return pLogger; }看起来仔细查过锁定版本好像能提供世界上最好的解决方案。只有少数几个在对象被实例化之前进入 GetLogger 的线程将被强行同步;以后到达的线程根本无需进入关键节。我们还想再要什么?仔细查过锁定模式尽管在概念上很简单,但事实证明,到目前为止,仍然难以正确编码。这是因为 C++ 标准没有定义线程模型。它假定只有一个执行线程,并且没有定义可供开发人员表示相关指令排序的约束的方式,这使得编译器可以自由重新排序内存的读取和写入。在多线程环境中,重新排序可能会导致线程在实际执行源代码中位置比它更靠前的所有语句之前就观察内存的写入。对于仔细查过锁定代码,可以在执行 Logger 构造函数之前,用分配给 Logger 对象的内存地址更新 pLogger 变量。观察到非空值的第二个线程将不再进入关键节,并返回未完全构造完成的对象的地址。(您可参见 Vance Morrison 撰写的关于托管代码的类似问题的文章“解读多线程应用程序中 Low-Lock 技术的影响”。)在旧版 Visual Studio®

  尽管 Visual Studio 2005 能正确实现仔细查过锁定模式,但还是很容易因为忽略包含实例指针的变量上的 volatile 限制符,或忽略关键节内的检查而使实现出错;而程序仍在编译并且似乎仍然有效。建立保证有效的机制将是对 C++ 有重要的补充。Windows Vista 没有坐等对标准主体的修订,而是提供了一个专门针对这一问题,称为一次性初始化的工具。不管您在哪一种硬件平台上使用 Windows,都能保证有效。一次性初始化既允许同步初始化,也允许异步初始化。让我们先来看看同步初始化。

  在概念上,同步初始化的工作模式与仔细查过锁定模式一样。在第一批 n 个试图同步执行初始化的线程中,实际上只有一个线程将实例化资源 — 其余线程将一直被阻止,直到初始化完成。一旦完成初始化,将不再阻止后面试图访问资源的线程;只会返回存储的资源。

  需要完成的第一件事情是声明并初始化 INIT_ONCE 结构的实例。您可使用 InitOnceInitialize 函数来执行初始化。对同步一次性初始化和异步一次性初始化而言,这是必须完成的,并且必须在任何其他一次性初始化函数使用结构之前完成。其定义如下:

  显示了上面使用的同一个 GetLogger 函数,但现在它被重新编写,以使用异步一次性初始化。我们将逐步解释本例程。线程做的第一件事是调用 InitOnceBeginInitialize。该线程必须提供指向待使用的 INIT_ONCE 结构的指针,并且 dwFlags 参数应设置为 INIT_ONCE_ASYNC,以指定此线程正尝试开始异步一次性初始化。通过使用其余两个函数,fPending 和 Context,将初始化状态传回给调用方,上述两个参数均被指定为指针以便函数可以更新它们。如果成功,InitOnceBeginInitialize 返回 TRUE,否则返回 FALSE,表示有些地方已经出错,初始化无法继续。必须检查 fPending 以确认其他线程是否已完成初始化。如果 fPending 是 FALSE,则初始化已经完成,并且已将初始化对象存入 Context 参数。在此例中,GetLogger 剩下的唯一工作就是将 Context 转换为 Logger* 并将它返回给调用方。

  在本文章中,我们向您介绍了 Windows Vista 中许多适于本机 C/C++ 开发人员的线程同步原语的重要增强功能。这些增强功能可让开发人员更容易地解决线程同步问题。但本文只涉及了一些皮毛。我们鼓励您更深入地研究它们以及其他更改。对于初学者,请先试着了解线程池 API 增强功能。很遗憾,我们没有机会在此讨论线程池 API 增强功能了,但它们确实是值得思考的宝贵的新增功能。

  为什么要进行线程同步?在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果...

  多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系...

  多线进程和线程是操作系统里面经常遇到的两个概念,还有一个概念,是应用程序。应用程序包括指令和数据,在开始运行之前,只是分布在磁盘上的指令和数据。正在执行的应用程序称为进程,进程不仅仅是指令和数据,它还有状...博文

  本文首先介绍了Java的线程基本操作(创建、等待等),线程的互斥、同步操作;然后解释了为什么需要互斥、同步,什么是信号(通知)丢失,什么是虚假唤醒;并实验展示了原子性(Atomic)问题、通知丢失(n...博文来自:xusiwei1236的技术博客

  本文解析了synchronized的底层实现,介绍了JVM中优化锁的一些方法,以及实际编程中使用的一些锁优化方法。...博文来自:

  阅读数 3787PV原语的含义P操作和V操作是不可中断的程序段,称为原语。PV原语及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量sem是一整数,sem大于等于零时代表可供并发进程使用的资源实...博文

  阅读数 187查看文章  查看文章  【流行秘语饰品】彻底解决工行U盾window...博文

  Windowsvista32Sp1整合官方中文旗舰版dowsVistaSP1刚刚已经有了新版本(Vista之家提示,2008年第一季度才有VistaSP1正式版本),现在就版本号问题做一个小小的算术,...博文来自:井中明月

  支持vista,是一款适合vista使用的,可用的虚拟机,请大家多支持啊!下载

  目前的WindowsVista已经可以从现有老版本的系统上升级,不过这要求系统盘具有至少15GB的剩余空间,并且要求两个系统地语种必须一致。除了升级安装,我们还可以进行全新安装,在进行全新安装的时候也...

  相信很多朋友都想升级到vista。但却困惑于不知道升级到vista的那个版本,面对vista的这么多版本,今天就让我们来给大家详尽介绍一下vista的各个版本的不同、系统的推荐要求以及基本功能。1、W...

  11-25阅读数 36微软讨论社区hive上出现了有趣的讨论题:购买到漂亮的Vista盒装机油桶包装后,有很多人会问该如何开包,你是怎么打开Windows Vista包装的?是小心翼翼地开,尽量保留...

  事先准备:装好xp系统的电脑一台,vista安装iso。    注:(1)WindowsVista要求安装在NTFS分区,但是其他分区可以是FAT32格式!    (2)简单无损NTFS转换方式:运行...博文来自:

  虽然TabletPC版的XP中也有墨球这个游戏,不过运行起来在游戏窗口范围内没有鼠标光标,不太爽.昨天无聊的时候又在尝试VAIO可以破解些什么,偶然发现这个游戏可以成功破解,没有任何问题,而且有鼠标光...博文来自:

  这篇文章适合于开发人员,普通Vista用户请飘过一下:)实际上,vista上不能安装Oracle11g的问题,和几年以前在IntelP4的机器上不能安装Oracle8.1.7一样,都是Java运行环境...

  这是我c+的课程设计。内容:完整c++代码(含测试代码),exe文件,完美的课程设计报告(优!)。 代码运行平台:visual studio 2005 ,窗体应用程序(windows application).br游戏仿照...

  引言:在很多用户在犹豫“我是否要安装Vista”时,有的网友采取了一个“折衷”的方案:模仿。通过一些第三方软件、主题包等,网友将WindowsXP模仿成WindowsVista界面的样子,在效果方...

  Vista/705-16世界之窗浏览器是由凤凰工作室出品的一款多标签、多窗口浏览器。它完全免费,没有任何功能限制,不捆绑任何第三方软件,可以干净卸载,请您放心使用。 世界之窗浏览器可运行于 Windows 2000/XP/2003/Vista/7 系列操作系统...下载

  圣诞献礼!          2012年-2013年,Sunny在CSDN技术博客中陆续发表了100多篇与设计模式相关的文章,涵盖了七个面向对象设计原则和24个设计模式(23个GoF设计模式 +  简...

  使用指南 阅读本指南前,请先阅读快速起步。本文档使用更复杂的场景进一步介绍Sharding-JDBC的分库分表能力。 数据库模式 本文档中提供了两个数据源db0和db1,每个数据源之...

  python图片处理类之~PIL.Image模块(ios android icon图标自动生成处理)

  帐号相关流程注册范围 企业 政府 媒体 其他组织换句话讲就是不让个人开发者注册。 :)填写企业信息不能使用和之前的公众号账户相同的邮箱,也就是说小程序是和微信公众号一个层级的。填写公司机构信息,对公账...

  0.绪论这篇文章主要为了研究双目立体视觉的最终目标——三维重建,系统的介绍了三维重建的整体步骤。双目立体视觉的整体流程包括:图像获取,摄像机标定,特征提取(稠密匹配中这一步可以省略),立体匹配,三维重...

  废话少说直接干货 在虚拟机里安装hadoop,关闭防火墙、配置ip 我的系统环境为:win8系统、vmware11、centos81、设置虚拟机网络通信模式为host only模式 在虚拟机目录...

  卷积神经网络是深度学习的基础,但是学习CNN却不是那么简单,虽然网络上关于CNN的相关代码很多,比较经典的是tiny_cnn(C++)、DeepLearnToolbox(Matlab)等等,但通过C语...

  单机最大的TCP连接数及其修改 一个误解: 单个服务器程序可承受最大连接数“理论”上是“65535” .    65535这个数字的由来,很多人想当然地将它与port最大值联系起来。的确,TCP的...

  Dll文件调用方式分为显式调用和隐式调用,在这里选择隐式调用的方式。 准备dll文件: 这里选择VS 2010作为dll的开发工具,打开vs 2010,选择文件-新建-项目 在新建项目窗...

  最近公司安排了个任务:要求交付一个DLL,实现【PDF转换成图片】,最好的效果是能够掌握所有源码,实在不行才交付第三方DLL。研究了6个工作日+1双休+每晚9点下班,逛遍了国内外各种论坛,还是没能成功...

  扫二维码关注,获取更多技术分享 本文承接之前发布的博客《 微信支付V3微信公众号支付PHP教程/thinkPHP5公众号支付》必须阅读上篇文章后才可以阅读这篇文章。由于最近一段时间工作比较忙,...

  最近项目空闲期,来看下spring源码,把过程全部记录下来, 方便想知道spring初始化过程初学者,先从spring作为入口。 org.springframework.web....

  1、错误:                 键盘遮挡输入框最常见的可能就是在登录界面了,无论有多少个textFiled,不论是在VC的任何位置。都有可能造成键盘弹出来时,把输入框挡住了。...

  众所周知,Activex组件没有进行有效的签名,在IE上无法安装的,除非你让用户手工开启“接收任何未签名的ActiveX”,这个很明显不现实。而组件签名需要证书,证书从哪里来,你可以选择付1000到3...

  上一篇博客介绍了如何解决Fragment重叠的问题,有需要的同学可以看一下,底部有demo下载。 直通车:完美解决Fragment重叠本篇博客我们来说一下怎么让fragment重新加载布局资源文件。...

  我们可能经常会用到这一功能,比如有时,我们不希望用户没有进行登录访问后台的操作页面,而且这样的非法访问会让系统极为的不安全,所以我们常常需要进行登录才授权访问其它页面,否则只会出现登录页面,当然我的思...

  MATLAB编程题 题目描述:从一个NxM的矩阵C中找出与1xM的矩阵P欧氏距离最小的某一行row,要求不能用循环!!! 输入:矩阵C(NxM)、矩阵P(1xM) 输出:row 解题思...

  自微软从2013版本推出新的插件注册器后,随着UI的重大更新后,问题也多了很多,前面已有博客提到注册assembly时看不到注册按钮(

  jquery/js实现一个网页同时调用多个倒计时(最新的) 最近需要网页添加多个倒计时. 查阅网络,基本上都是千遍一律的不好用. 自己按需写了个.希望对大家有用. 有用请赞一个哦! //js ...

  强连通分量: 简言之 就是找环(每条边只走一次,两两可达) 孤立的一个点也是一个连通分量   使用tarjan算法 在嵌套的多个环中优先得到最大环( 最小环就是每个孤立点)   定义: int Ti...

  工作需求,渐变色的边框和文字,还需要显示drawable。我们知道如果是View的背景色渐变,那么很好办,只需要写一个drawable文件,里面定义shape然后设置为View的background就...

  command窗口是命令窗口,即为sqplus窗口,有命令提示符,识别sqlplus命令,基本的命令都可以执行 sql仅可执行DDL、select、DML等...

  上一篇文章讲解了SNMP的基本架构,本篇文章将重点分析SNMP报文,并对不同版本(SNMPv1、v2c、v3)进行区别! 四、SNMP协议数据单元 在SNMP管理中,管理站(NMS)和代理(Age...

  iOS安全攻防(二十三):Objective-C代码混淆class-dump可以很方便的导出程序头文件,不仅让攻击者了解了程序结构方便逆向,还让着急赶进度时写出的欠完善的程序给同行留下笑柄。所以,我们...

  测试环境莫名其妙有几条重要数据被删除了,由于在binlog里面只看到是公用账号删除的,无法查询是那个谁在那个时间段登录的,就考虑怎么记录每一个MYSQL账号的登录信息,在MYSQL中,每个连接都会先执...

  前言 以前在博客:基于PYQT编写一个人脸识别软件 中给出了我自己用PYQT编写的一个小软件。鉴于使用的是开源库——face_recogniton,尽管使用很简单,但是还有些问题,比如:识别黄种人时...

  最近在做一个Mac下的ssh调试工具,但是出现了一点问题。后来发现居然Mac下有串口调试工具可以用,所以果断换串口了,是普通PL2303芯片的usb转串口线。 接下来说下简单的安装步骤吧。我是勤劳的...

  oqqsoap1234567:这只能让一行高亮,sendmessage一次,之前的就消失了,怎么让多行高亮?

  oqqsoap1234567:这只能让一行高亮,sendmessage一次,之前的就消失了,怎么让多行高亮?

  u013453581:好厉害!像楼主学习,本人现在也在准备微策略的笔试。

本文链接:http://chuyenchame.com/duruyuanyu/191.html