一、同步上下文(SynchronizationContext)概述

由来

多线程程序在.net框架出现之前就已经存在了。这些程序通常需要一个线程将一个工作单元传递给另一个线程。Windows程序以消息循环为中心,因此许多程序员使用这个内置队列来传递工作单元。每个想要以这种方式使用Windows消息队列的多线程程序都必须定义自己的自定义Windows消息和处理它的约定。

当.net框架首次发布时,这种通用模式被标准化了。那时,. net支持的唯一GUI应用程序类型是Windows窗体。然而,框架设计者预期了其他模型,他们开发了一个通用的解决方案。ISynchronizeInvoke诞生了。而SynchronizationContext就是用来取代ISynchronizeInvoke。

1、概念

同步上下文:这里同步是动词,据有归类的功能,假如有A(UI线程)、B类两线程,B线程要更新A线程的内容。如果直接在B中更新A中内容,那就是B线程多管闲事了,增加程序的耦合。为了降低程序的耦合度,B线程必须把更新A线程UI的事情还给A线程, 以消息方式把

要更改内容发送给A线程,A线程有一个堆栈用来保存B线程发送过来的消息。然后A线程根据自己情况决定什么时候更新。

如果B线程以Send()方法给A线程发送消息,B线程发送消息后什么事情都不做一直等待A线程的回复(同步),对应SynchronizationContext.Send()方法。

如果B线程以Post()方法给A线程发送消息,B线程发送完消息后就去做其他事情了(异步)),对应SynchronizationContext.Post()方法(asp.net 除外)。

同步上下文是一种可以将工作单元(执行某些方法 多播委托)排队到上下文(主要是不同的线程)的方法。
它的作用通俗来讲就是实现线程之间通讯的。

同步上下文应用于很多场景,比如在WinForms和WPF中,只有一个UI线程可以更新UI元素(文本框,复选框等)。如果尝试从另一个非UI线程更改文本框的内容,则不会发生更改,也可能抛出异常(取决于UI框架)。因此,在这样的应用程序中,非UI线程需要将对UI元素的所有更改安排到UI线程。这就是同步上下文提供的内容。它允许将一个工作单元(执行某些方法)发布到不同的上下文
- 在这种情况下是UI线程。

注意:cpu每30毫秒切换一次

2、作用域

无论是什么平台(ASP.NET、Windows 窗体、Windows Presentation Foundation (WPF)、Silverlight 或其他),所有 .NET 程序都包含 SynchronizationContext 概念。

Microsoft
.NET
Framework提供了同步上下文的SynchronizationContext类。根据平台框架不同,又单独提供了WindowsFormsSynchronizationContext(WinForm)类、DispatcherSynchronizationContext(WPF)类等同步上下文的模型但都是继承自SynchronizationContext类。

每个线程都有一个默认的SynchronizationContext,但是不是每个线程都附加SynchronizationContext.Current这个对象,只有UI线程是一直拥有的SynchronizationContext.Current。故获取SynchronizationContext.Current也只能在UI线程上进行SynchronizationContext context = SynchronizationContext.Current;

//只有UI线程能获取到 值,这是创建一个副本,不同ExecutionContent.capture,
SynchronizationContext maincontent= SynchronizationContext.Current;//这是创建一个副本。ExecutionContent.capture是捕获引用
//asp.net 和控制获取的结果是null
SynchronizationContext maincontent= SynchronizationContext.Current;

3、原理

通过UI线程与工作线程的时序图可以看出整个更新的步骤:

整个过程中关键的是主线程的SynchronizationContext,SynchronizationContext在通讯中充当传输者的角色。在线程执行过程中,需要更新到UI控件上的数据不再直接更新,而是通过UI线程上下文的Post/Send方法,将数据以异步/同步消息的形式发送到UI线程的消息队列;UI线程收到该消息后,根据消息是异步消息还是同步消息来决定通过异步/同步的方式调用SetTextSafePost方法直接更新自己的控件了。在本质上,向UI线程发送的消息并是不简单数据,而是一条委托调用命令。

4、作用:传输者

传输者SynchronizationContext在通讯中充当传输者的角色(用Post/Send方法实现传输),实现功能就是一个线程和另外一个线程的通讯。SynchronizationContext将UI线程的同步环境保存下来,让这个环境可以在不同的线程之间流动,其他非UI线程可以用这个环境回到要ui线程执行的任务。例如在winform应用中,非UI线程中利用这个环境更新UI控件的内容,而不是利用控件的invoke方法。 SynchronizationContext.post()表示启用一个新线程来执行委托(异步执行)。SynchronizationContext.send()表示在当前线程执行(同步的)。SynchronizationContext.post是同步上下文最重要的一个方法。

Send:发送界面更新请求至主线程,阻塞当前线程直至返回。SynchronizationContext.Send(SendOrPostCallback d,object state): 
Post:发送界面更新请求至主线程,不阻塞当前线程。SynchronizationContext.Post(SendOrPostCallback d,object state);
public
delegate void SendOrPostCallback(object state);d
为一个没有返回值,并且具有一个Object类型传入参数的委托(SendOrPostCallback );state
为执行这个委托时的参数(object);

注意:
  SynchronizationContext的对象不是所有线程都被附加的,只有UI主线程会被附加。
  对于UI线程来说,是如何将SynchronizationContext这个对象附加到线程上的呢?
  在Form1 form = new Form1()之前,SynchronizationContext对象是为空,而当实例化Form1窗体后,SynchronizationContext对象就被附加到这个线程上了。
  所以可以得出答案了:当Control对象被创建的同时,SynchronizationContext对象也会被创建并附加到线程上。所以在使用时,一定要等窗体InitializeComponent(); 这个完成后 它才能得到一个不是NULL的对象.

5、应用:那么如何编写我的组件与UI框架无关呢?

看过很多介绍文章介绍如何在后台线程更新主界面的,多数都是使用Control.Invoke, Control.BeginInvoke。这些都是很好的解决方案,不过有两个问题:

1. 必须的引用System.Windows.Forms,然后 using System.Windows.Forms

2. 代码结构比较零乱。(实际上这个也是#1 导致的)

微软提供了另外一个比较优雅的解决方案,就是 System.Threading. SynchronizationContext。 可以看出,它不在
namesapce System.Windows.Forms 里面,因此我们可以理直气壮地用在BusinessLaryer,
Controler,甚至 module 里面。

其实在UI线程中使用的并不是SynchronizationContext这个类,而是WindowsFormsSynchronizationContext这个东东,它是SynchronizationContext的派生类。

以下是SynchronizationContext在winform中实际应用:

案例一

案例二

二、同步上下文的派生

同步上下文的实际"上下文"没有明确定义。不同的框架和主机可以自由地定义自己的上下文。了解这些不同的实现及其局限性,可以准确地阐明同步上下文概念的作用和不足之处。我将简要讨论其中的一些实现。

1、WinForm 的同步上下文

WindowsFormsSynchronizationContext继承自SynchronizationContext
命名空间:System.Windows.Forms.dll:System.Windows.Forms

实现:

Windows Forms应用程序将创建WindowsFormsSynchronizationContext并将其安装为创建UI控件的任何线程的当前上下文。
此SynchronizationContext在UI控件上使用ISynchronizeInvoke方法,该控件将委托传递到基础Win32消息循环。
WindowsFormsSynchronizationContext的上下文是单个UI线程(单线程的)。
排队到WindowsFormsSynchronizationContext的所有委托一次执行一次;它们由特定的UI线程按排队顺序执行。当前实现为每个UI线程创建一个WindowsFormsSynchronizationContext。

为了避免死锁,就要防止异步线程切换道ui线程,代码要这样写:

async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}

2、WPF框架中同步上下文

源代码

using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using MS.Win32; public sealed class DispatcherSynchronizationContext : SynchronizationContext
{
internal Dispatcher _dispatcher; private DispatcherPriority _priority; public DispatcherSynchronizationContext()
: this(Dispatcher.CurrentDispatcher, DispatcherPriority.Normal)
{
} public DispatcherSynchronizationContext(Dispatcher dispatcher)
: this(dispatcher, DispatcherPriority.Normal)
{
} public DispatcherSynchronizationContext(Dispatcher dispatcher, DispatcherPriority priority)
{
if (dispatcher == null)
{
throw new ArgumentNullException("dispatcher");
}
Dispatcher.ValidatePriority(priority, "priority");
_dispatcher = dispatcher;
_priority = priority;
SetWaitNotificationRequired();
} public override void Send(SendOrPostCallback d, object state)
{
if (BaseCompatibilityPreferences.GetInlineDispatcherSynchronizationContextSend() && _dispatcher.CheckAccess())
{
_dispatcher.Invoke(DispatcherPriority.Send, d, state);
}
else
{
_dispatcher.Invoke(_priority, d, state);
}
} public override void Post(SendOrPostCallback d, object state)
{
_dispatcher.BeginInvoke(_priority, d, state);
} public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (_dispatcher._disableProcessingCount > 0)
{
return UnsafeNativeMethods.WaitForMultipleObjectsEx(waitHandles.Length, waitHandles, waitAll, millisecondsTimeout, false);
}
return SynchronizationContext.WaitHelper(waitHandles, waitAll, millisecondsTimeout);
} public override SynchronizationContext CreateCopy()
{
if (BaseCompatibilityPreferences.GetReuseDispatcherSynchronizationContextInstance())
{
return this;
}
if (BaseCompatibilityPreferences.GetFlowDispatcherSynchronizationContextPriority())
{
return new DispatcherSynchronizationContext(_dispatcher, _priority);
}
return new DispatcherSynchronizationContext(_dispatcher, DispatcherPriority.Normal);
}
}

DispatcherSynchronizationContext继承自SynchronizationContext
命名空间:WindowsBase.dll:System.Windows.Threading

实现:

Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。
WPF和Silverlight应用程序使用DispatcherSynchronizationContext,该代理将对UI线程的Dispatcher的委托以“Normal”优先级排队。
当线程通过调用Dispatcher.Run开始循环调度器 ,将这个初始化完成的 同步上下文 安装到当前上下文。
DispatcherSynchronizationContext的上下文是单个UI线程(单线程的

排队到DispatcherSynchronizationContext的所有委托均由特定的UI线程一次按其排队的顺序执行。当前实现为每个顶级窗口创建一个DispatcherSynchronizationContext,即使它们都共享相同的基础Dispatcher。

为了避免死锁,就要防止异步线程切换道ui线程,代码要这样写:

async Task DoAsync()
{
await Task.Run(() => { }).ConfigureAwait(false);
}

3、默认同步上下文

源代码

// System.Threading.SynchronizationContext
using System;
using System.Runtime.CompilerServices;
using System.Threading; [NullableContext(1)]
[Nullable(0)]
public class SynchronizationContext
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c(); [TupleElementNames(new string[] { "d", "state" })]
public static Action<ValueTuple<SendOrPostCallback, object>> <>9__8_0; internal void <Post>b__8_0([TupleElementNames(new string[] { "d", "state" })] ValueTuple<SendOrPostCallback, object> s)
{
s.Item1(s.Item2);
}
} private bool _requireWaitNotification; [Nullable(2)]
public static SynchronizationContext Current
{
[NullableContext(2)]
get
{
return Thread.CurrentThread._synchronizationContext;
}
} private static int InvokeWaitMethodHelper(SynchronizationContext syncContext, IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
} protected void SetWaitNotificationRequired()
{
_requireWaitNotification = true;
} public bool IsWaitNotificationRequired()
{
return _requireWaitNotification;
} public virtual void Send(SendOrPostCallback d, [Nullable(2)] object state)
{
d(state);
} public virtual void Post(SendOrPostCallback d, [Nullable(2)] object state)
{
ThreadPool.QueueUserWorkItem(<>c.<>9__8_0 ?? (<>c.<>9__8_0 = new Action<ValueTuple<SendOrPostCallback, object>>(<>c.<>9.<Post>b__8_0)), new ValueTuple<SendOrPostCallback, object>(d, state), false);
} public virtual void OperationStarted()
{
} public virtual void OperationCompleted()
{
} [CLSCompliant(false)]
public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
return WaitHelper(waitHandles, waitAll, millisecondsTimeout);
} [CLSCompliant(false)]
protected static int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout)
{
if (waitHandles == null)
{
throw new ArgumentNullException("waitHandles");
}
return WaitHandle.WaitMultipleIgnoringSyncContext(waitHandles, waitAll, millisecondsTimeout);
} [NullableContext(2)]
public static void SetSynchronizationContext(SynchronizationContext syncContext)
{
Thread.CurrentThread._synchronizationContext = syncContext;
} public virtual SynchronizationContext CreateCopy()
{
return new SynchronizationContext();
}
}

(默认)SynchronizationContext位于:mscorlib.dll:System.Threading

Default SynchronizationContext 是默认构造的 SynchronizationContext 对象。

  • 根据惯例,如果一个线程的当前 SynchronizationContext 为 null,那么它隐式具有一个Default SynchronizationContext
  • Default SynchronizationContext 将其异步委托列队到 ThreadPool ,但在调用线程上直接执行其同步委托
  • 因此,Default SynchronizationContext涵盖所有 ThreadPool 线程以及任何调用 Send 的线程。
  • 这个上下文“借助”调用 Send 的线程们,将这些线程放入这个上下文,直至委托执行完成
    • 从这种意义上讲,默认上下文可以包含进程中的所有线程
  • Default SynchronizationContext 应用于 线程池 线程,除非代码由 ASP.NET 承载。
  • Default SynchronizationContext 还隐式应用于显式子线程(Thread 类的实例),除非子线程设置自己的 SynchronizationContext 。

因此,UI 应用程序通常有两个同步上下文:

  • 包含 UI 线程的 UI SynchronizationContext
  • 包含 ThreadPool 线程的Default SynchronizationContext

默认SynchronizationContext是多线程的

默认的同步上下文只是将任务安排到线程池操作队列

public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
} public virtual void Send(SendOrPostCallback d, [Nullable(2)] object state)
{
d(state);
}
public delegate void SendOrPostCallback(object state);

设置同步上下文

using System;
using System.Threading;
using System.Threading.Tasks; namespace GetSetContext
{
internal static class Program
{
public static async Task Main(string[] args)
{
Console.WriteLine($"Current Synchronization Context: {SynchronizationContext.Current}"); SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext()); Console.WriteLine($"Current Synchronization Context: {SynchronizationContext.Current}"); await Task.Delay(100); Console.WriteLine("Completed!");
} private class MySynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
Console.WriteLine($"Continuation dispatched to {nameof(MySynchronizationContext)}");
d.Invoke(state);
}
}
}
}

这段代码输出以下内容:

Current Synchronization Context: null
Current Synchronization Context: MySynchronizationContext
Continuation dispatched to MySynchronizationContext
Completed!

上下文捕获和执行

BackgroundWorker运行流程

  • 首先BackgroundWorker 捕获使用调用 RunWorkerAsync 的线程的 同步上下文
  • 然后,在Default SynchronizationContext中执行DoWork
  • 最后,在之前捕获的上下文中执行其 RunWorkerCompleted 事件

UI同步上下文 中只有一个 BackgroundWorker ,因此 RunWorkerCompletedRunWorkerAsync 捕获UI同步上下文中执行(如下图)。

UI同步上下文中的嵌套 BackgroundWorker

  • 嵌套: BackgroundWorker 从其 DoWork 处理程序启动另一个 BackgroundWorker

    • 嵌套的 BackgroundWorker 不会捕获 UI同步上下文
  • DoWork线程池 线程使用 默认同步上下文 执行。
    • 在这种情况下,嵌套的 RunWorkerAsync 将捕获默认 SynchronizationContext
    • 因此它将由一个 线程池 线程而不是 UI线程 执行其 RunWorkerCompleted
    • 这样会导致异步执行完后,后面的代码就不在UI同步上下文中执行了(如下图)。

默认情况下,控制台应用程序Windows服务 中的所有线程都只有 Default SynchronizationContext,这会导致一些基于事件异步组件失败(也就是没有UI同步上下文的特性)

  • 要解决这个问题,可以创建一个显式子线程,然后将 UI同步上下文 安装在该线程上,这样就可以为这些组件提供上下文。
  • Nito.Async 库的 ActionThread 类可用作通用同步上下文实现。
 

二、ExecutionContext 上下文的捕获和恢复

三、ExecutionContext 概述

1、ExecutionContext 实际上只是其他上下文的容器。因此在.net framework中ExecutionContext 包含:同步上下文、安全上下文、调用上下文、模拟上下文、区域性通常会与执行上下文一起流动。

2、在.net core中, 不支持安全上下文和调用上下文、同步上下文和ExecutionContex一起流动。

1、2两条内容来源:https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.executioncontext?view=net-6.0

ExecutionContext 实际上是一个 state 包

  • 用于从一个线程上捕获所有 state
  • 然后在控制逻辑流的同时将其还原到另一个线程

ExecutionContext 是使用静态方法 Capture 捕获的:

// 周围环境的 state 捕获到 ec 中
ExecutionContext ec = ExecutionContext.Capture();

通过静态方法 Run ,在委托(Run方法的参数)调用时恢复 ExecutionContext

ExecutionContext.Run(ec, delegate
{
… // 这里的代码将上述 ec 的状态视为周围环境
}, null);

所有派生异步工作的方法都以这种方式捕获还原 ExecutionContext 的。

  • 带有“Unsafe”字样的方法除外,它们是不安全的,因为它们不传播 ExecutionContext

例如:

  • 当您使用 Task.Run 时,对 Run 的调用将从调用线程捕获 ExecutionContext ,并将该 ExecutionContext 实例存储到 Task 对象中
  • 当提供给 Task.Run 的委托作为该 Task 执行的一部分被调用时,它是使用存储的 ExecutionContext 通过 ExecutionContext.Run 来完成的

以下所有异步API的执行都是捕获 ExecutionContext 并将其存储,然后在调用某些代码时再使用存储的 ExecutionContext

  • Task.Run
  • ThreadPool.QueueUserWorkItem
  • Delegate.BeginInvoke
  • Stream.BeginRead
  • DispatcherSynchronizationContext.Post
  • 任何其他异步API

当我们谈论“flowing ExecutionContext”时,我们实际上是在讨论:

  • 在一个线程上获取周围环境状态
  • 在稍后的某个时刻将该状态恢复到另一个线程上(需要执行提供的委托的线程)。

ExecutionContext类创建副本以便传播

  • ExecutionContext无法在另一个线程上设置与线程关联的。 尝试这样做将导致引发异常。 若要将 ExecutionContext 从一个线程传播到另一个线程,请创建的副本 ExecutionContext
  • ExecutionContext类提供  CreateCopy 创建当前执行上下文的副本。

三.   ExecutionContext 和 SynchronizationContext使用区别

前面我们介绍了 SynchronizationContext 是如何调度线程的,现在,我们要进行进行一次对比:

  • flowing ExecutionContext 在语义上与 capturing and posting to a SynchronizationContext 完全不同。
  • ExecutionContext 的流动无法控制、这是框架故意这样设计的,开发人员在编写异步代码时不必担心 ExecutionContext ;它在基础架构级别上的支持,有助于在异步环境中模拟同步方式的语义(即TLS);

  • SynchronizationContext的流动是可以控制的,可以通过task.ConfigureAwait(bool) 传入false关闭为 false ,则等待者(awaiter) 不检查 SynchronizationContext ,就像没有一样

  • 当 ExecutionContext 流动时,您是从一个线程捕获 state ,然后还原该 state
    • 使提供的委托执行时处于周围环境 state
  • 当您捕获使用 SynchronizationContext 时,不会发生这种情况。
    • 捕获部分是相同的,因为您要从当前线程中获取数据,但是随后用不同方式使用 state
    • SynchronizationContext.Post 只是使用捕获的状态来调用委托,而不是在调用委托时设置该状态为当前状态
      • 委托在何时何地以及如何运行完全取决Post方法的实现

         
         
         
         

参考文章:

https://www.cnblogs.com/BigBrotherStone/p/12240731.html#%E4%B8%8A%E4%B8%8B%E6%96%87%E6%8D%95%E8%8E%B7%E5%92%8C%E6%89%A7%E8%A1%8C

https://blog.csdn.net/starrycraft/article/details/113658608

https://docs.microsoft.com/zh-cn/archive/msdn-magazine/2011/february/msdn-magazine-parallel-computing-it-s-all-about-the-synchronizationcontext

https://blog.csdn.net/kalvin_y_liu/article/details/117787437?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.nonecase

【C# TAP 异步编程】四、SynchronizationContext 同步上下文|ExecutionContext的更多相关文章

  1. 从执行上下文角度重新理解.NET(Core)的多线程编程[2]:同步上下文

    一般情况下,我们可以将某项操作分发给任意线程来执行,但有的操作确实对于执行的线程是有要求的,最为典型的场景就是:GUI针对UI元素的操作必须在UI主线程中执行.将指定的操作分发给指定线程进行执行的需求 ...

  2. SynchronizationContext(同步上下文)综述

    >>返回<C# 并发编程> 1. 概述 2. 同步上下文 的必要性 2.1. ISynchronizeInvoke 的诞生 2.2. SynchronizationContex ...

  3. 【C# TAP 异步编程】三、async\await的运作机理详解

    [原创] 本文只是个人笔记,很多错误,欢迎指出. 环境:vs2022  .net6.0 C#10 参考:https://blog.csdn.net/brook_shi/article/details/ ...

  4. 执行上下文与同步上下文 | ExecutionContext 和 SynchronizationContext

    原文连接:执行上下文与同步上下文 - .NET 并行编程 (microsoft.com) 执行上下文与同步上下文 斯蒂芬 6月15日, 2012 最近,我被问了几次关于 ExecutionContex ...

  5. 【C# TAP 异步编程】一 、async 修饰符(标记)

    async的作用: 1.async是一个标记,告诉编译器这是一个异步方法. 2.编译器会根据这个标志生成一个异步状态机. 3.编译器将原异步方法中的代码清空,写入状态机的配置,原先异步方法中的代码被封 ...

  6. 深入理解JS异步编程四(HTML5 Web Worker)

    >Web Workers 是 HTML5 提供的一个javascript多线程解决方案,我们可以将一些大计算量的代码交由web Worker运行而不冻结用户界面. 一:如何使用Worker We ...

  7. 【C#TAP 异步编程】构造函数 OOP

    原文:异步 OOP 2:构造函数 (stephencleary.com) 异步构造带来了一个有趣的问题.能够在构造函数中使用会很有用,但这意味着构造函数必须返回一个表示将来将构造的值,而不是构造的值. ...

  8. 【C# TAP 异步编程】二 、await运算符已经可等待类型Awaitable

    await的作用: 1.await是一个标记,告诉编译器生成一个等待器来等待可等待类型实例的运行结果. 2.一个await对应一个等待器 ,任务的等待器类型是TaskAwaiter/TaskAwait ...

  9. 【C#TAP 异步编程】异步接口 OOP

    在我们深入研究"异步OOP"之前,让我们解决一个相当常见的问题:如何处理异步方法的继承?那么"异步接口"呢? 幸运的是,它确实可以很好地与继承(和接口)一起使用 ...

随机推荐

  1. sort排序出现安卓与苹果系统排序不一致问题

    sort排序出现安卓与苹果系统排序不一致问题

  2. linux 创建用户 用户组,sudo,禁止root远程ssh登录

    创建用户  useradd hanli 为新用户设置密码(在root下可以为普通用户重置密码)  passwd hanli 创建用户组  groupadd  op 将用户添加到用户组  usermod ...

  3. 深入理解F1-score

    本博客的截图均来自zeya的post:Essential Things You Need to Know About F1-Score | by Zeya | Towards Data Science ...

  4. Java 中的锁原理、锁优化、CAS、AQS 详解!(转)

    1.为什么要用锁? 锁-是为了解决并发操作引起的脏读.数据不一致的问题. 2.锁实现的基本原理 2.1.volatile Java编程语言允许线程访问共享变量, 为了确保共享变量能被准确和一致地更新, ...

  5. DLL链接库

    转载请注明来源:https://www.cnblogs.com/hookjc/ 2. 静态链接库 对静态链接库的讲解不是本文的重点,但是在具体讲解 DLL 之前,通过一个静态链接库的例子可以快速地帮助 ...

  6. Embedded Python应用小结

    转载请注明来源:https://www.cnblogs.com/hookjc/ (1)初始化Python脚本运行环境 Py_Initialize(); (2) 脚本的编译 bytecode = Py_ ...

  7. 利用纯代码写出一个秒表表盘的方法 —— #DF

    @interface ViewController () @property (nonatomic, strong) CALayer *secLayer; // 秒针layer @property ( ...

  8. web虚拟主机、日志分割以及日志分析

    目录 一.构建虚拟web主机 1.1 概述 1.2 支持的虚拟主机类型 1.3 部署虚拟主机步骤 1.3.1 基于域名的虚拟主机 (1)为虚拟主机提供域名解析 (2)为虚拟主机准备网页文档 (3)添加 ...

  9. Azure AD Domain Service(二)为域服务中的机器配置 Azure File Share 磁盘共享

    一,引言 Azure File Share 是支持两种认证方式的! 1)Active Directory 2)Storage account key 记得上次分析的 "Azure File ...

  10. spring security中当已登录用户再次访问登录界面时,应跳转到home

    @RequestMapping("/login") public String login(){ Authentication auth = SecurityContextHold ...