物聯網操作系統Zephyr入門教程4調度(scheduling)

来源:https://www.cnblogs.com/testing-/archive/2023/07/08/17537410.html
-Advertisement-
Play Games

一、影響 Linux 性能的各種因素 1、系統硬體資源 (1)CPU 如何判斷多核 CPU 與超線程 消耗 CPU 的業務:動態 web 服務、mail 服務 (2)記憶體 物理記憶體與 swap 的取捨 選擇 64 位 Linux 操作系統 消耗記憶體的業務:記憶體資料庫(redis/hbase/mong ...


什麼是調度

調度器決定哪個線程被允許在任何時間點上執行;這個線程被稱為當前線程。

在不同的時間點有機會改變當前線程的身份。這些點被稱為重新安排點。一些潛在的重排點是:

  • 從運行狀態過渡到暫停或等待狀態,例如通過k_sem_take()或k_sleep()。
  • 過渡到準備狀態,例如通過k_sem_give()或k_thread_start()。
  • 處理完中斷後返回到線程上下文
  • 調用k_yield()

當線程主動發起將自身轉換為暫停或等待狀態的操作時,它就會進入睡眠狀態。

每當調度器改變了當前線程的身份,或者當前線程的執行被ISR所取代時,內核會首先保存當前線程的CPU寄存器值。當線程後來恢復執行時,這些寄存器的值會被恢復。

調度器是如何工作的?

內核的調度器選擇最高優先順序的就緒線程作為當前線程。當存在多個相同優先順序的就緒線程時,調度器會選擇等待時間最長的那個。

註意:ISR的執行要優先於線程的執行。除非中斷被屏蔽了,否則當前的線程可以在任何時候被ISR取代。

內核在構建時可以選擇幾種就緒隊列的實現方式之一。這種選擇是一種權衡:

  • 代碼大小
  • 恆定繫數的運行時間開銷
  • 當涉及到許多線程時的性能擴展

你的Kconfig文件(prj.conf)應該包含以下內容之一(或者它將預設為簡單的鏈接列表)。

隊列類型:

  • 簡單鏈接列表準備隊列(CONFIG_SCHED_DUMB)
    • 簡單無序列表
    • 對於單線程來說,具有非常快的恆定時間性能
    • 代碼大小非常低
    • 對有以下情況的系統有用:
      • 有限的代碼大小
      • 在任何時候都有少量的線程(<=3)。
  • 紅/黑樹就緒隊列(CONFIG_SCHED_SCALABLE)
    • 紅/黑樹
    • 較慢的恆定時間插入和移除開銷
    • 需要額外的2Kb代碼
    • 可以乾凈利落地擴展到成千上萬的線程
    • 適用於有以下情況的系統:許多併發的可運行線程(>20個左右)。
  • 傳統的多隊列就緒隊列(CONFIG_SCHED_MULTIQ)
    • 經典的列表陣列,每個優先順序一個(最多32個優先順序)。
    • 與 "dumb "調度器相比,其代碼開銷很小
    • 在0(1)時間內運行,恆定繫數很低
    • 需要相當大的RAM預算來存儲列表頭
    • 與截止日期調度和SMP親和性不相容
    • 有少量線程的系統(但通常DUMB已經足夠好了)。
  • 可擴展的wait_q實現(CONFIG_WAITQ_SCALABLE)。
  • 簡單的鏈接列表wait_q (CONFIG_WAITQ_DUMB)

線程優先順序是如何工作的?

線程的優先順序是整數值,可以是負數,也可以是非負數。從數字上看,較低的優先順序優先於較高的值(-5>6)。

調度器根據每個線程的優先順序來區分兩類線程。

  • 協作(cooperative)線程有負的優先順序值。一旦它成為當前線程,合作線程就一直是當前線程,直到它執行了使其unready的動作。
  • 可搶占的線程有非負的優先權值。一旦成為當前線程,如果協作線程或更高或同等優先順序的可搶占線程準備就緒,可搶占線程可以在任何時候被取代。

線程的初始優先順序值可以在該線程啟動後被向上或向下改變。因此,可搶占的線程有可能成為協作線程,反之亦然。

內核支持幾乎無限數量的線程優先順序。配置選項CONFIG_NUM_COOP_PRIORITIES和CONFIG_NUM_PREEMPT_PRIORITIES為每一類線程指定了優先順序的數量,導致以下可用的優先順序範圍:

  • 協作性線程:(-CONFIG_NUM_COOP_PRIORITIES)到-1
  • 搶占式線程: 0到(CONFIG_NUM_PREEMPT_PRIOTIES-1)

什麼是協作式時間切分?

因此,如果協作線程執行冗長的計算,它可能會導致其他線程的調度出現不可接受的延遲,包括那些更高的優先順序。

為了剋服這樣的問題,協作線程可以不時地自願放棄CPU,以允許其他線程執行。線程可以通過兩種方式放棄CPU:

  • 調用k_yield()將線程放在調度器的就緒線程優先順序列表的後面,然後調用調度器。所有優先順序高於或等於該線程的就緒線程被允許在該線程被重新安排之前執行。如果沒有這樣的線程存在,調度器會立即重新安排該線程,而不進行上下文切換。
  • 調用k_sleep()使線程在指定的時間段內unready。然後,所有優先順序的就緒線程都被允許執行;然而,並不保證優先順序低於休眠線程的線程會在休眠線程再次變得就緒之前實際被調度。

什麼是搶占式時間切分?

圖中顯示了同等優先順序的線程相互搶占的情況

搶占式線程可以執行合作性的時間切分(如上所述),或者利用調度器的時間切分能力來允許其他相同優先順序的線程執行。

調度器將時間劃分為一系列的時間片,這裡的時間片是以系統時鐘刻度來衡量的。時間片的大小是可配置的,但這個大小可以在應用程式運行時改變。

在每個時間片結束時,調度器會檢查當前線程是否是可搶占的,如果是,就隱式地代表線程調用k_yield()。這給了其他相同優先順序的準備好的線程在當前線程再次被調度之前執行的機會。如果沒有相同優先順序的線程準備好了,那麼當前線程仍然保留。

優先順序高於指定限制的線程不受搶占式時間切分的影響,也不會被同等優先順序的線程搶占。這允許應用程式只在處理對時間不太敏感的低優先順序線程時使用搶占式時間切分。

註意:內核的時間切分演算法並不能確保一組同等優先順序的線程獲得公平的CPU時間,因為它並不衡量線程實際得到的執行時間的多少。然

不希望在執行關鍵操作時被搶占的可預選線程可以通過調用k_sched_lock()指示調度器暫時將其作為協作線程。這可以防止其他線程在執行關鍵操作時受到干擾。

一旦關鍵操作完成,可搶占的線程必須調用k_sched_unlock()來恢復其正常的、可搶占的狀態。

如果線程調用k_sched_lock(),並隨後執行了unready的操作,調度器將把鎖定的線程切換出來,並允許其他線程執行。當鎖定的線程再次成為當前線程時,它的不可搶占狀態將被保持。

註意:對於可搶占的線程來說,鎖定調度器是一種比將其優先順序改為負值更有效的防止搶占的方法。

實例:協作式時間切分

線程_1,即使優先順序較低,也不會讓給線程_2(直到完成)。

#include <zephyr/zephyr.h>

/* size of stack area used by each thread */
#define STACKSIZE 1024

#define PRIORITY_THREAD_1	(-1)
#define PRIORITY_THREAD_2	(-2)


K_THREAD_STACK_DEFINE(thread_1_stack_area, STACKSIZE);
static struct k_thread thread_1_data;

K_THREAD_STACK_DEFINE(thread_2_stack_area, STACKSIZE);
static struct k_thread thread_2_data;

void thread_1(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	int i = 0;

	printk("thread_1: thread started \n");
	k_thread_start(&thread_2_data);
	printk("thread_1: thead_2 started \n");

	while (1)
	{
		i++;
		printk("thread_1: thread loop %d\n", i);
		if (i == 3)
		{
			printk("thread_1: thread abort\n");
			k_thread_abort(&thread_1_data);
		}
	}

}

void thread_2(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	int i = 0;

	printk("thread_2: thread started \n");

	while (1)
	{
		i++;
		printk("thread_2: thread loop %d\n", i);
		if (i == 3)
		{
			printk("thread_2: thread abort\n");
			k_thread_abort(&thread_2_data);
		}
	}

}

void main(void)
{
	k_thread_create(&thread_1_data, thread_1_stack_area,
			K_THREAD_STACK_SIZEOF(thread_1_stack_area),
			thread_1, NULL, NULL, NULL,
			PRIORITY_THREAD_1, 0, K_FOREVER);
	k_thread_name_set(&thread_1_data, "thread_1");

	k_thread_create(&thread_2_data, thread_2_stack_area,
			K_THREAD_STACK_SIZEOF(thread_2_stack_area),
			thread_2, NULL, NULL, NULL,
			PRIORITY_THREAD_2, 0, K_FOREVER);
	k_thread_name_set(&thread_2_data, "thread_2");

	k_thread_start(&thread_1_data);
}

執行結果:

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.4.0-554-g33b116407b03 ***
thread_1: thread started 
thread_1: thead_2 started 
thread_1: thread loop 1
thread_1: thread loop 2
thread_1: thread loop 3
thread_1: thread abort
thread_2: thread started 
thread_2: thread loop 1
thread_2: thread loop 2
thread_2: thread loop 3
thread_2: thread abort

實例:搶占式(不同優先順序)

#include <zephyr/zephyr.h>

/* size of stack area used by each thread */
#define STACKSIZE 1024

#define PRIORITY_THREAD_1	(3)
#define PRIORITY_THREAD_2	(2)
#define PRIORITY_THREAD_3 	(1)


K_THREAD_STACK_DEFINE(thread_1_stack_area, STACKSIZE);
static struct k_thread thread_1_data;

K_THREAD_STACK_DEFINE(thread_2_stack_area, STACKSIZE);
static struct k_thread thread_2_data;

K_THREAD_STACK_DEFINE(thread_3_stack_area, STACKSIZE);
static struct k_thread thread_3_data;

void thread_1(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	int i = 0;

	printk("thread_1: thread started \n");
	k_thread_start(&thread_2_data);

	while (1)
	{
		i++;
		printk("thread_1: thread loop %d\n", i);
		if (i == 3)
		{
			printk("thread_1: thread abort\n");
			k_thread_abort(&thread_1_data);
		}
	}

}

void thread_2(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	int i = 0;

	printk("thread_2: thread started \n");
	k_thread_start(&thread_3_data);

	while (1)
	{
		i++;
		printk("thread_2: thread loop %d\n", i);
		if (i == 3)
		{
			printk("thread_2: thread abort\n");
			k_thread_abort(&thread_2_data);
		}
	}

}

void thread_3(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);

	int i = 0;

	printk("thread_3: thread started \n");

	while (1)
	{
		i++;
		printk("thread_3: thread loop %d\n", i);
		if (i == 3)
		{
			printk("thread_3: thread abort\n");
			k_thread_abort(&thread_3_data);
		}
	}

}

void main(void)
{
	k_thread_create(&thread_1_data, thread_1_stack_area,
			K_THREAD_STACK_SIZEOF(thread_1_stack_area),
			thread_1, NULL, NULL, NULL,
			PRIORITY_THREAD_1, 0, K_FOREVER);
	k_thread_name_set(&thread_1_data, "thread_1");

	k_thread_create(&thread_2_data, thread_2_stack_area,
			K_THREAD_STACK_SIZEOF(thread_2_stack_area),
			thread_2, NULL, NULL, NULL,
			PRIORITY_THREAD_2, 0, K_FOREVER);
	k_thread_name_set(&thread_2_data, "thread_2");

	k_thread_create(&thread_3_data, thread_3_stack_area,
			K_THREAD_STACK_SIZEOF(thread_3_stack_area),
			thread_3, NULL, NULL, NULL,
			PRIORITY_THREAD_3, 0, K_FOREVER);
	k_thread_name_set(&thread_3_data, "thread_3");

	k_thread_start(&thread_1_data);
}

執行結果:

 ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.4.0-554-g33b116407b03 ***
thread_1: thread started 
thread_2: thread started 
thread_3: thread started 
thread_3: thread loop 1
thread_3: thread loop 2
thread_3: thread loop 3
thread_3: thread abort
thread_2: thread loop 1
thread_2: thread loop 2
thread_2: thread loop 3
thread_2: thread abort
thread_1: thread loop 1
thread_1: thread loop 2
thread_1: thread loop 3
thread_1: thread abort

實例:搶占式(相同優先順序)

include <zephyr/zephyr.h>

/* size of stack area used by each thread */
#define STACKSIZE 1024

#define PRIORITY_THREAD_1	(1)
#define PRIORITY_THREAD_2	(1)
#define PRIORITY_THREAD_3 	(1)


K_THREAD_STACK_DEFINE(thread_1_stack_area, STACKSIZE);
static struct k_thread thread_1_data;

K_THREAD_STACK_DEFINE(thread_2_stack_area, STACKSIZE);
static struct k_thread thread_2_data;

K_THREAD_STACK_DEFINE(thread_3_stack_area, STACKSIZE);
static struct k_thread thread_3_data;

void thread_1(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);
	
	int i = 0;
	printk("thread_1: thread started \n");
	

	while (1)
	{
		i++;
		printk("thread_1: thread loop %d\n", i);
		if (i == 300)
		{
			printk("thread_1: thread abort\n");
			k_thread_abort(&thread_1_data);
		}
	}

}

void thread_2(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);
	
	int i = 0;
	printk("thread_2: thread started \n");

	while (1)
	{
		i++;
		printk("thread_2: thread loop %d\n", i);
		if (i == 300)
		{
			printk("thread_2: thread abort\n");
			k_thread_abort(&thread_2_data);
		}
	}

}

void thread_3(void *dummy1, void *dummy2, void *dummy3)
{
	ARG_UNUSED(dummy1);
	ARG_UNUSED(dummy2);
	ARG_UNUSED(dummy3);
	
	int i = 0;
	printk("thread_3: thread started \n");

	while (1)
	{
		i++;
		printk("thread_3: thread loop %d\n", i);
		if (i == 300)
		{
			printk("thread_3: thread abort\n");
			k_thread_abort(&thread_3_data);
		}
	}

}

void main(void)
{
	k_thread_create(&thread_1_data, thread_1_stack_area,
			K_THREAD_STACK_SIZEOF(thread_1_stack_area),
			thread_1, NULL, NULL, NULL,
			PRIORITY_THREAD_1, 0, K_FOREVER);
	k_thread_name_set(&thread_1_data, "thread_1");

	k_thread_create(&thread_2_data, thread_2_stack_area,
			K_THREAD_STACK_SIZEOF(thread_2_stack_area),
			thread_2, NULL, NULL, NULL,
			PRIORITY_THREAD_2, 0, K_FOREVER);
	k_thread_name_set(&thread_2_data, "thread_2");

	k_thread_create(&thread_3_data, thread_3_stack_area,
			K_THREAD_STACK_SIZEOF(thread_3_stack_area),
			thread_3, NULL, NULL, NULL,
			PRIORITY_THREAD_3, 0, K_FOREVER);
	k_thread_name_set(&thread_3_data, "thread_3");

	k_thread_start(&thread_1_data);
	k_thread_start(&thread_2_data);
	k_thread_start(&thread_3_data);
}

執行結果

$ ninja run
[0/1] To exit from QEMU enter: 'CTRL+a, x'[QEMU] CPU: qemu32,+nx,+pae
SeaBIOS (version zephyr-v1.0.0-0-g31d4e0e-dirty-20200714_234759-fv-az50-zephyr)
Booting from ROM..
*** Booting Zephyr OS build zephyr-v3.4.0-554-g33b116407b03 ***
thread_1: thread started 
thread_1: thread loop 1
...
thread_1: thread loop 66
threthread_2: thread started 
thread_2: thread loop 1
...
thread_2: thread loop 101
ththread_3: thread started 
thread_3: thread loop 1
...
thread_3: thread loop 101
thad_1: thread loop 67
thread_1: thread loop 68
...
thread_1: thread loop 164
threadread_2: thread loop 102
thread_2: thread loop 103
...
thread_2: thread loop 197
thread_2: threaread_3: thread loop 102
thread_3: thread loop 103
...
thread_3: thread loop 197
thread_3: threa_1: thread loop 165
thread_1: thread loop 166
...
thread_1: thread loop 260
thread_1: thread lood loop 198
thread_2: thread loop 199
...
thread_2: thread loop 293
thread_2: thread loop 2d loop 198
thread_3: thread loop 199
...
thread_3: thread loop 293
thread_3: thread loop 2p 261
thread_1: thread loop 262
...
thread_1: thread loop 300
thread_1: thread abort
94
thread_2: thread loop 295
...
thread_2: thread loop 300
thread_2: thread abort
94
thread_3: thread loop 295
...
thread_3: thread loop 300
thread_3: thread abort

參考資料

什麼是線程睡眠?

線程可以調用k_sleep()來延遲它的處理過程,直到指定的時間段。線上程休眠期間,CPU被放棄以允許其他準備好的線程執行。一旦指定的延遲時間過了,線程就會準備好,並有資格再次被調度。

沉睡的線程可以被另一個線程用k_wakeup()提前喚醒。這種技術有時可以用來允許輔助線程向沉睡的線程發出信號,告訴它有事情發生了,而不需要線程定義內核同步對象,比如semaphore。喚醒沒有睡眠的線程是允許的,但沒有任何效果。

什麼是忙於等待?

線程可以調用k_busy_wait()來執行繁忙等待,在指定的時間段內延遲其處理,而不把CPU讓給其他準備好的線程。

當所需的延遲時間太短,以至於調度器的上下文從當前線程切換到另一個線程,然後再返回時,通常會使用繁忙等待來代替線程睡眠。

協作式還是搶占式?

  • 設備驅動和其他性能關鍵的工作 -> 協作線程
  • 使用協作線程來實現互斥,而不需要內核對象,例如mutex。
  • 使用搶占式線程,使時間敏感的處理優先於時間不敏感的處理。
釘釘或微信號: pythontesting 微信公眾號:pythontesting
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # Unity中的PostProcessScene:深入解析與實用案例 在Unity游戲開發中,我們經常需要對場景進行後處理,以實現更豐富的視覺效果。Unity提供了一個名為`PostProcessScene`的功能,可以讓我們在場景載入完成後,對場景進行一系列的處理。本文將詳細介紹`PostPro ...
  • IQueryable/IQueryable 和表達式樹 IQueryable有兩個組件 Expression:當前查詢的組件的與語言和數據源無關的表示形式,以表達式樹的形式表示。 Provider:LINQ 提供程式的實例,它知道如何將當前查詢具體化為一個值或一組值。 ![](https://img ...
  • 特性 跨平臺 Web API和MVC技術的統一 原生依賴的註入支持 更強的測試性 輕量、高性能的模塊 開源、有社區的支持 項目入口--Main Asp.Net Core應用程式最初作為控制台應用程式啟動,而Program.cs文件中的Main()方法就是入口 CreateHostBuilder()方 ...
  • ## 一:背景 ### 1. 講故事 很多朋友可能會有疑問,C# 是一門托管語言,怎麼可能會有非托管句柄泄露呢? 其實一旦 C# 程式與 C++ 語言交互之後,往往就會被後者拖入非托管泥潭,讓我們這些調試者被迫探究 `非托管領域問題`。 ## 二:非托管句柄泄露 ### 1. 測試案例 為了方便講述 ...
  • # Unity UGUI的所有組件的介紹及使用 本文將介紹Unity UGUI中的各個組件,包括它們的具體介紹、用途 ## 1. Text(文本) - 介紹:Text組件用於在UI界面上顯示文本內容。 - 用途:常用於顯示UI界面的標題、按鈕標簽、提示信息等。 ## 2. Image(圖片) - 介 ...
  • # Unity AssetPostprocessor中Model相關函數的實際應用 Unity AssetPostprocessor是Unity引擎中的一個重要功能,它可以在導入資源時自動一些腳本,以便對資源進行自定義處理。其中,Model相關的函數可以用於對導入的3D模型進行處理,包括修改模型的材 ...
  • 國產系統大勢所趨,如果你公司的winform界面軟體需要在linux上運行,如果軟體是用C#開發的,現在我有一個好的快速解決方案。 世界第一的微軟的Microsoft Visual Studio,確實好用,C# 開發起來確實效率高,不過微軟的開發語言開發的軟體的界面都是跟windows系統綁定的,現 ...
  • 2013年7月1日,痞子衡應屆畢業正式入職飛思卡爾半導體上海 Site,至今已經十年零七天。 上周六是整十年的日子,當時並沒有特別的感覺,但是過去的一周總有種情愫在醞釀,終於今天還是決定花點時間回憶下過去的十年,梳理下那些值得紀念的時刻。 ### Offer抉擇 時間撥回到 2012 年的秋天,痞子 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...