一文说清Task及其调度问题

news2025/4/26 22:13:03

ask对于.NET的重要性毋庸置疑。通过最近的一些面试人员经历,发现很多人对与Task及其调度机制,以及线程和线程池之间的关系并没有清晰的认识。本文采用最简单的方式模拟了Task的实现,旨在说明Task是什么?它是如何被调度执行的?

一、Task(Job)

Task代表一项具有某种状态的操作,我们使用如下这个Job类型来模拟Task。Job封装的操作体现为一个Action委托,状态则通过JobStatus枚举来表示(对应TaskStatus枚举)。简单起见,我们仅仅定义了四种状态(创建、调度、执行和完成)。Invoke方法负责执行封装的Action委托,并对状态进行相应设置。

public class Job
{
    private readonly Action _work;
    public Job(Action work)=> _work = work;
    public JobStatus Status { get; internal set; }

    internal protected virtual void Invoke()
    {
        Status = JobStatus.Running;
        _work();
        Status = JobStatus.Completed;    

    }
}

public enum JobStatus
{
    Created,
    Scheduled,
    Running,
    Completed
}

二、TaskScheduler(JobScheduler)

Task承载的操作通过调度得以执行,具体的调度策略取决于调度器的选择。Task调度器通过TaskScheduler表示,我们利用如下这个JobScheduler类型对它进行模拟。如下面的代码片段所示,我们只为抽象类JobScheduler定义了唯一的QueueJob方法来调度作为参数的Job对象。静态Current属性表示当前默认实现的调度器。

public abstract class JobScheduler
{
    public abstract void QueueJob(Job job);
    public static JobScheduler Current { get; set; } = new ThreadPoolJobScheduler ();
}

对于开发者来说,执行Task就是将它提交给调度器,这一操作体现在我们为Job类型定义的静态Start方法中。该方法通过参数指定具体的调度器,如果没有显式指定,默认采用JobScheduler的Current静态属性设置的默认调度器。为了方便后面的演示,我们还定义了一个静态的Run方法,该方法会将指定的Action对象封装成Job,并调用Start方法利用默认的调度器进行调度。

public class Job
{
    private readonly Action _work;
    public Job(Action work)=> _work = work;
    public JobStatus Status { get; internal set; }

    internal protected virtual void Invoke()
    {
        Status = JobStatus.Running;
        _work();
        Status = JobStatus.Completed;

    }

    public void Start(JobScheduler? scheduler = null) => (scheduler ?? JobScheduler.Current).QueueJob(this);
    public static Job Run(Action work)
    {
        var job = new Job(work);
        job.Start();
        return job;
    }
}

三、基于线程池的调度

Task如何执行取决于选择怎样的调度器,.NET默认采用基于线程池的调度策略,这一策略体现在ThreadPoolTaskScheduler类型上,我们使用如下这个ThreadPoolJobScheduler 进行模拟。如下面的代码片段所示,重写的QueueJob方法通过调用ThreadPool.QueueUserWorkItem方法执行指定Job对象封装的Action委托。JobScheduler的Current属性设置的默认调度器就是这么一个ThreadPoolJobScheduler 对象。

public class ThreadPoolJobScheduler : JobScheduler
{
    public override void QueueJob(Job job)
    {
        job.Status = JobStatus.Scheduled;
        var executionContext = ExecutionContext.Capture();
        ThreadPool.QueueUserWorkItem(_ => ExecutionContext.Run(executionContext!, _ => job.Invoke(), null));
    }
}

我们按照如下的方式调用Job的静态Run方法创建并执行了三个Job,每个Job封装的Action委托在执行的时候会将当前线程ID打印出来。

_ = Job.Run(() => Console.WriteLine($"Job1 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job2 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job3 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));

Console.ReadLine();

由于采用默认的基于线程池的调度策略,所以三个Job会在三个不同的线程上执行。

四、使用指定线程进行调度

我们知道.NET进程只有一个全局的线程池,对于一些需要长时间运行且具有较高优先级的操作,采用基于线程池的调用未必是好的选择。比如在一个Web应用中,线程池的工作线程会被用来处理请求,对于一个需要持续运行的Job可能会因为可用工作线程的不足而被阻塞。.NET对于这种情况具有不同的处理方式(启动Task的时候选择TaskCreationOptions.LongRunning选项),这里我们使用自定义调度器的方式来解决这个问题。如下这个DedicatedThreadJobScheduler 利用创建的“专有线程”来保证被调用的Job能够“立即”执行。

internal class DedicatedThreadJobScheduler : JobScheduler
{
    private readonly BlockingCollection<Job> _queues = new();
    private readonly Thread[] _threads;
    public DedicatedThreadJobScheduler(int threadCount)
    {
        _threads = Enumerable.Range(1, threadCount).Select(i_ => new Thread(Invoke)).ToArray();
        Array.ForEach(_threads, it => it.Start());
        void Invoke(object? state)
        {
            while (true)
            {
                _queues.Take().Invoke();
            }
        }
    }
    public override void QueueJob(Job job)=>_queues.Add(job);
}

还是上面演示的程序,这次我们将当前调度器设置为上面这个DedicatedThreadJobScheduler ,并将使用的线程数设置为2。

JobScheduler.Current = new DedicatedThreadJobScheduler (2);
_ = Job.Run(() => Console.WriteLine($"Job1 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job2 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job3 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job4 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job5 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));
_ = Job.Run(() => Console.WriteLine($"Job6 is excuted in thread {Thread.CurrentThread.ManagedThreadId}"));

Console.ReadLine();

我们会发现所有的操作只会在两个固定的线程中被执行。

五、异步等待

如果需要在某个Task执行之后接着执行后续的操作,我们可以调用其ContinueWith方法指定待执行的操作,现在我们将这个方法定义Job类型上。Job与Task的ContinueWith有些差异,在这里我们认为ContinueWith指定的也是一个Job,那么多个Job则可以按照预先编排的顺序构成一个链表。当前Job执行后,只需要将后续这个Job交付给调度器就可以了。如下面的代码片段所示,我们利用_continue字段来表示异步等待执行的Job,并利用它维持一个Job链表。ContinueWith方法会将指定的Action委托封装成Job并添加到链表末端。

public class Job
{
    private readonly Action _work;
    private Job? _continue;
    public Job(Action work) => _work = work;
    public JobStatus Status { get; internal set; }
    public void Start(JobScheduler? scheduler = null) => (scheduler ?? JobScheduler.Current).QueueJob(this);
    internal protected virtual void Invoke()
    {
        Status = JobStatus.Running;
        _work();
        Status = JobStatus.Completed;
        _continue?.Start();
    }

    public static Job Run(Action work)
    {
        var job = new Job(work);
        job.Start();
        return job;
    }

    public Job ContinueWith(Action<Job> continuation)
    {
        if (_continue == null)
        {
            var job = new Job(() => continuation(this));
            _continue = job;
        }
        else
        {
            _continue.ContinueWith(continuation);
        }
        return this;
    }
}

利用ContinueWith方法实现异步操作的按序执行体现在如下的程序中。

Job.Run(() =>{
    Thread.Sleep(1000);
    Console.WriteLine("Foo1");
}).ContinueWith(_ =>{
    Thread.Sleep(100);
    Console.WriteLine("Bar1");
}).ContinueWith(_ =>{
    Thread.Sleep(100);
    Console.WriteLine("Baz1");
});



Job.Run(() =>{
    Thread.Sleep(100);
    Console.WriteLine("Foo2");
}).ContinueWith(_ =>{
    Thread.Sleep(10);
    Console.WriteLine("Bar2");
}).ContinueWith(_ =>{
    Thread.Sleep(10);
    Console.WriteLine("Baz2");
});


Console.ReadLine();

输出结果

六、await关键字的运用

虽然ContinueWith方法能够解决“异步等待”的问题,但是我们更喜欢使用await关键字,接下来我们就为Job赋予这个能力。为此我们定义了如下这个实现了ICriticalNotifyCompletion接口的JobAwaiter结构体。顾名思义,该接口用来发送操作完成的通知。一个JobAwaiter对象由一个Job对象构建而成,当它自身执行完成之后,OnCompleted方法会被调用,我们利用它执行后续的操作。

public struct JobAwaiter: ICriticalNotifyCompletion
{
    private readonly Job _job;
    public bool IsCompleted => _job.Status ==  JobStatus.Completed;
    public JobAwaiter(Job job)
    {
        _job = job;
        if (job.Status == JobStatus.Created)
        {
            job.Start();
        }
    }
    public void OnCompleted(Action continuation)
    {
        _job.ContinueWith(_ => continuation());
    }
    public void GetResult() { }
    public void UnsafeOnCompleted(Action continuation)=>OnCompleted(continuation);
}

我们在Job类型上添加这个GetAwaiter方法返回根据自身创建的JobAwaiter对象。

public class Job
{
    private readonly Action _work;
    private Job? _continue;
    public Job(Action work) => _work = work;
    public JobStatus Status { get; internal set; }
    public void Start(JobScheduler? scheduler = null) => (scheduler ?? JobScheduler.Current).QueueJob(this);
    internal protected virtual void Invoke()
    {
        Status = JobStatus.Running;
        _work();
        Status = JobStatus.Completed;
        _continue?.Start();
    }


    public static Job Run(Action work)
    {
        var job = new Job(work);
        job.Start();
        return job;
    }
    public Job ContinueWith(Action<Job> continuation)
    {
        if (_continue == null)
        {
            var job = new Job(() => continuation(this));
            _continue = job;
        }
        else
        {
            _continue.ContinueWith(continuation);
        }
        return this;
    }
    public JobAwaiter GetAwaiter() => new(this);
}

任何一个类型一旦拥有了这样一个GetAwaiter方法,我们就能将await关键词应用在对应的对象上面。

await Foo();
await Bar();
await Baz();


Console.ReadLine();

static Job Foo() =>  new Job(() =>
{
    Thread.Sleep(1000);
    Console.WriteLine("Foo");
});

static Job Bar() => new Job(() =>
{
    Thread.Sleep(100);
    Console.WriteLine("Bar");
});

static Job Baz() => new Job(() =>
{
    Thread.Sleep(10);
    Console.WriteLine("Baz");
});

输出结果:

七、状态机

我想你应该知道await关键字仅仅是编译器提供的语法糖,编译后的代码会利用一个“状态机”实现“异步等待”的功能,上面这段代码最终编译成如下的形式。值得一提的是,Debug和Release模式编译出来的代码是不同的,下面给出的是Release模式下的编译结果,上述的状态机体现为生成的<<Main>$>d__0这个结构体。它的实现其实很简单:如果个方法出现了N个await关键字,它们相当于将整个方法的执行流程切割成N+1段,状态机的状态体现为当前应该执行那段,具体的执行体现在MoveNext方法上。GetAwaiter方法返回的ICriticalNotifyCompletion对象用来确定当前操作是否结束,如果结束则可以直接指定后续操作,否则需要调用AwaitUnsafeOnCompleted对后续操作进行处理。

// Program
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Jobs;

[CompilerGenerated]
internal class Program
{
	[StructLayout(LayoutKind.Auto)]
	[CompilerGenerated]
	private struct <<Main>$>d__0 : IAsyncStateMachine
	{
		public int <>1__state;

		public AsyncTaskMethodBuilder <>t__builder;

		private JobAwaiter <>u__1;

		private void MoveNext()
		{
			int num = <>1__state;
			try
			{
				JobAwaiter awaiter;
				switch (num)
				{
				default:
					awaiter = <<Main>$>g__Foo|0_0().GetAwaiter();
					if (!awaiter.IsCompleted)
					{
						num = (<>1__state = 0);
						<>u__1 = awaiter;
						<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
						return;
					}
					goto IL_006c;
				case 0:
					awaiter = <>u__1;
					<>u__1 = default(JobAwaiter);
					num = (<>1__state = -1);
					goto IL_006c;
				case 1:
					awaiter = <>u__1;
					<>u__1 = default(JobAwaiter);
					num = (<>1__state = -1);
					goto IL_00c6;
				case 2:
					{
						awaiter = <>u__1;
						<>u__1 = default(JobAwaiter);
						num = (<>1__state = -1);
						break;
					}
					IL_00c6:
					awaiter.GetResult();
					awaiter = <<Main>$>g__Baz|0_2().GetAwaiter();
					if (!awaiter.IsCompleted)
					{
						num = (<>1__state = 2);
						<>u__1 = awaiter;
						<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
						return;
					}
					break;
					IL_006c:
					awaiter.GetResult();
					awaiter = <<Main>$>g__Bar|0_1().GetAwaiter();
					if (!awaiter.IsCompleted)
					{
						num = (<>1__state = 1);
						<>u__1 = awaiter;
						<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref this);
						return;
					}
					goto IL_00c6;
				}
				awaiter.GetResult();
				Console.ReadLine();
			}
			catch (Exception exception)
			{
				<>1__state = -2;
				<>t__builder.SetException(exception);
				return;
			}
			<>1__state = -2;
			<>t__builder.SetResult();
		}

		void IAsyncStateMachine.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			this.MoveNext();
		}

		[DebuggerHidden]
		private void SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
		{
			<>t__builder.SetStateMachine(stateMachine);
		}

		void IAsyncStateMachine.SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
		{
			//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
			this.SetStateMachine(stateMachine);
		}
	}

	[AsyncStateMachine(typeof(<<Main>$>d__0))]
	private static Task <Main>$(string[] args)
	{
		<<Main>$>d__0 stateMachine = default(<<Main>$>d__0);
		stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
		stateMachine.<>1__state = -1;
		stateMachine.<>t__builder.Start(ref stateMachine);
		return stateMachine.<>t__builder.Task;
	}

	[SpecialName]
	private static void <Main>(string[] args)
	{
		<Main>$(args).GetAwaiter().GetResult();
	}
}

上面提到过,编译器生成的状态机代码在Debug和Release模式是不一样的。在Release模式下状态机是一个结构体,虽然是以接口ICriticalNotifyCompletion的方式使用它,但是由于使用了ref关键字,所以不会涉及装箱,所以不会对GC造成任何影响。但是Debug模式下生成的状态机则是一个类(如下所示),将会涉及针对堆内存的分配和回收。对于遍布await关键字的应用程序,两者之间的性能差异肯定是不同的。实际上针对Task的很多优化策略,比如使用ValueTask,对某些Task<T>对象(比如状态为Completed的Task<bool>对象)的复用,以及使用IValueTaskSource等,都是为了解决内存分配的问题。

// Program
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Jobs;

[CompilerGenerated]
internal class Program
{
	[CompilerGenerated]
	private sealed class <<Main>$>d__0 : IAsyncStateMachine
	{
		public int <>1__state;

		public AsyncTaskMethodBuilder <>t__builder;

		public string[] args;

		private JobAwaiter <>u__1;

		private void MoveNext()
		{
			int num = <>1__state;
			try
			{
				JobAwaiter awaiter3;
				JobAwaiter awaiter2;
				JobAwaiter awaiter;
				switch (num)
				{
				default:
					awaiter3 = <<Main>$>g__Foo|0_0().GetAwaiter();
					if (!awaiter3.IsCompleted)
					{
						num = (<>1__state = 0);
						<>u__1 = awaiter3;
						<<Main>$>d__0 stateMachine = this;
						<>t__builder.AwaitUnsafeOnCompleted(ref awaiter3, ref stateMachine);
						return;
					}
					goto IL_007e;
				case 0:
					awaiter3 = <>u__1;
					<>u__1 = default(JobAwaiter);
					num = (<>1__state = -1);
					goto IL_007e;
				case 1:
					awaiter2 = <>u__1;
					<>u__1 = default(JobAwaiter);
					num = (<>1__state = -1);
					goto IL_00dd;
				case 2:
					{
						awaiter = <>u__1;
						<>u__1 = default(JobAwaiter);
						num = (<>1__state = -1);
						break;
					}
					IL_00dd:
					awaiter2.GetResult();
					awaiter = <<Main>$>g__Baz|0_2().GetAwaiter();
					if (!awaiter.IsCompleted)
					{
						num = (<>1__state = 2);
						<>u__1 = awaiter;
						<<Main>$>d__0 stateMachine = this;
						<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
						return;
					}
					break;
					IL_007e:
					awaiter3.GetResult();
					awaiter2 = <<Main>$>g__Bar|0_1().GetAwaiter();
					if (!awaiter2.IsCompleted)
					{
						num = (<>1__state = 1);
						<>u__1 = awaiter2;
						<<Main>$>d__0 stateMachine = this;
						<>t__builder.AwaitUnsafeOnCompleted(ref awaiter2, ref stateMachine);
						return;
					}
					goto IL_00dd;
				}
				awaiter.GetResult();
				Console.ReadLine();
			}
			catch (Exception exception)
			{
				<>1__state = -2;
				<>t__builder.SetException(exception);
				return;
			}
			<>1__state = -2;
			<>t__builder.SetResult();
		}

		void IAsyncStateMachine.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			this.MoveNext();
		}

		[DebuggerHidden]
		private void SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
		{
		}

		void IAsyncStateMachine.SetStateMachine([System.Runtime.CompilerServices.Nullable(1)] IAsyncStateMachine stateMachine)
		{
			//ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
			this.SetStateMachine(stateMachine);
		}
	}

	[AsyncStateMachine(typeof(<<Main>$>d__0))]
	[DebuggerStepThrough]
	private static Task <Main>$(string[] args)
	{
		<<Main>$>d__0 stateMachine = new <<Main>$>d__0();
		stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
		stateMachine.args = args;
		stateMachine.<>1__state = -1;
		stateMachine.<>t__builder.Start(ref stateMachine);
		return stateMachine.<>t__builder.Task;
	}

	[SpecialName]
	[DebuggerStepThrough]
	private static void <Main>(string[] args)
	{
		<Main>$(args).GetAwaiter().GetResult();
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/614278.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JUC源码分析:ReentrantLock

ReentrantLock进行上锁的流程如下图所示&#xff0c;我们将按照下面的流程分析ReentrantLock上锁的流程。 先进入ReentrantLock.lock方法。 再进入内部类NonfairSync的lock方法。 点击acquire方法进入AbstractQueuedSynchronizer.acquire方法。 进入tryAcquire方法回到Reentra…

【小林计网笔记】 IP篇

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 在这里插入图片描述 一、IP 基本认识1、IP的作用2、IP与MAC的关系 二、IP 地址的基础知识1、IP地址的定义2、IP地址的分类1、ABC类地址2、DE类地址3、IP地址分类的优…

rtthread系统中lwgps软件包的使用

开发环境&#xff1a;gd32f450开发板 嵌入式操作系统&#xff1a;rtthread 软件包&#xff1a;lwgps gps定位模块&#xff1a;正点原子ATK-1218-BD lwgps软件包的介绍&#xff1a;此项目是完成开源的lwgps与rt-thread的匹配。原工程地址&#xff1a;https://github.com/MaJerle…

linux 系统-备份与恢复

linux 系统-备份与恢复 基本介绍 实体机无法做快照&#xff0c;如果系统出现异常或者数据损坏&#xff0c;后果严重&#xff0c; 要重做系统&#xff0c;还会造成数据丢失。所以我们可以使用备份和恢复技术 linux 的备份和恢复很简单&#xff0c; 有两种方式&#xff1a; 把…

论文浅尝 | 通过对比学习优化用于命名实体识别的双编码器

笔记整理&#xff1a;陆星宇&#xff0c;东南大学硕士&#xff0c;研究方向为自然语言处理 链接&#xff1a;https://arxiv.org/abs/2208.14565 动机 命名实体识别&#xff08;NER&#xff09;是识别与命名实体相关的文本片段并将其分类到预定义的实体类型&#xff08;如人物、…

MegEngine 使用小技巧:用 mperf 进行安卓 opencl 算子的 roofline 分析

前言 roofline 分析是一种简单评估当前计算任务对当前平台计算/访存能力的利用情况的方法&#xff0c;可以帮助分析算子的优化方向和优化潜力。mperf 实现了安卓 mali/adreno 两种 gpu 平台的 roofline 分析能力&#xff0c;下面以 mali 平台为例&#xff0c;简单介绍一下操作步…

【操作系统】什么是用户态和内核态?用户态和内核态是如何切换的?

【操作系统】什么是用户态和内核态&#xff1f;用户态和内核态是如何切换的&#xff1f; 参考资料&#xff1a; 用户态到内核态切换 什么是用户态和内核态&#xff1f; 「操作系统」什么是用户态和内核态&#xff1f;为什么要区分 一、什么是用户态和内核态&#xff1f; 1.1、…

案例23:基于Java宿舍管理系统设计和实现开题报告

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

Echarts的x轴调整间隔,可以用 xAxis数组子项的axisLabel.interval

Echarts的x轴调整间隔,可以用 xAxis数组子项的axisLabel.interval https://echarts.apache.org/zh/option.html#xAxis.axisLabel.interval xAxis.axisLabel.interval auto 默认值是’auto’ 可设置为 : number 或 Function 数字或函数 坐标轴刻度标签的显示间隔&#xff0c;…

逍遥自在学C语言 | for循环详解

前言 C语言中的循环结构时&#xff0c;for循环是最常用的一种。它允许重复执行一段代码&#xff0c;直到满足特定条件为止。 本文将详细介绍for循环的用法&#xff0c;并提供相关的可编译运行的C代码示例。 一、人物简介 第一位闪亮登场&#xff0c;有请今后会一直教我们C语…

生产·成本

短期生产成本 给定生产技术&#xff0c;管理者必须选择如何生产&#xff08;即使用什么样的要素组合&#xff09;使生产成本最小。 衡量成本 边际成本 长期成本与规模 长期平均成本&#xff08;LAC&#xff09;和长期边际成本&#xff08;LMC&#xff09; 规模经济 随着产…

【Web网站服务】Apache网页优化

Apache网页优化 一、网页压缩1.1网页压缩步骤 二、网页缓存三、隐藏版本信息五、Apache防盗链 一、网页压缩 在企业中&#xff0c;部署Apache后只采用默认的配置参数&#xff0c;会引发网站很多问题&#xff0c;换言之默认配置是针对以前较低的服务器配置的&#xff0c;以前的…

chatgpt赋能python:Python如何拆分list

Python如何拆分list 在Python编程中&#xff0c;我们经常需要对list进行拆分操作。拆分list可以帮助我们更好地处理数据&#xff0c;以便进一步进行分析、计算或可视化呈现。本文将介绍Python中如何拆分list&#xff0c;并且提供一些常用的拆分方式。 按固定大小进行拆分 我…

【AIGC】12、DINO | 针对 DETR-like 检测器的提升

文章目录 一、背景二、方法2.1 Contrastive DeNoising Training2.3 Mixed Query Selection2.4 Look Forward Twice 三、效果 论文&#xff1a;DINO: DETR with Improved DeNoising Anchor Boxes for End-to-End Object Detection 代码&#xff1a;https://github.com/IDEACVR/…

【JUC基础】15. Future模式

目录 1、前言 2、什么是Future 2.1、传统程序调用 3、JDK中的Future 3.1、Future相关API 3.2、FutureTask 3.2.1、FutureTask实现 3.2.2、FutureTask相关API 3.3、CompletableFuture 3.3.1、thenApply 3.3.2、异步任务编排之thenCompose() 3.3.3、异步任务编排之th…

南京市某高校计算机科学与技术专业性能测试与Loadrunner—考试试卷分析

XXX科技学院试卷 20 /20 学年 第 学期 课程所属部门&#xff1a; 课程名称&#xff1a; 课程编号&#xff1a; 考试方式&#xff1a;&#xff08;A、B、开、闭&#xff09;卷 使用班级&#xff1a; …

数据安全--16--数据采集阶段安全防护措施

本博客地址&#xff1a;https://security.blog.csdn.net/article/details/131033616 一、引子 数据安全采集阶段的防护措施主要是从三个方面来开展的&#xff0c;第一个是从个人数据主体采集方面&#xff0c;第二个是从外部机构采集方面&#xff0c;以上两个方面基本涵盖了数…

读改变未来的九大算法笔记05_数字签名

1. 数字签名”&#xff08;Digital Signature&#xff09; 1.1. 单词数字化&#xff08;digital&#xff09;意味着其“由数字字符串组成” 1.2. 任何数字化的东西都能被拷贝 1.3. “签名”的全部意义在于能被读取&#xff0c;但不能被除了作者的任何人拷贝&#xff08;也就…

linux(信号产生中)理论部分

1.实际执行信号的处理动作称为信号递达 2.信号从产生到递达之间的状态&#xff0c;称为信号未决&#xff08;Pending&#xff09; 3.进程可以选择阻塞某个信号的 ----------------------------------------------------------------------------------------------------------…

【JavaSE】Java(五十一):核心要点总结

文章目录 1. String str "love"; 和 String str new String("love);一样吗&#xff1f;2. 如何将字符串反转3. String类的常用方法有哪些?4. new String("sn") new String("ow")会创建几个对象&#xff1f; 1. String str “love”; 和…