主頁 > 後端開發 > 深入學習:三分鐘快速教會你撰寫執行緒安全代碼!

深入學習:三分鐘快速教會你撰寫執行緒安全代碼!

2020-12-30 06:25:24 後端開發

相信有很多同學在面對多執行緒代碼時都會望而生畏,認為多執行緒代碼就像一頭難以馴服的怪獸,你制服不了這頭怪獸它就會反過來吞噬你,

夸張了哈,總之,多執行緒程式有時就像一潭淤泥,走不進去退不出來,

可這是為什么呢?為什么多執行緒代碼如此難以正確撰寫呢

從根源上思考

關于這個問題,本質上是有一個詞語你沒有透徹理解,這個詞就是所謂的執行緒安全,thread safe,

如果你不能理解執行緒安全,那么給你再多的方案也是無用武之地

接下來我們了解一下什么是執行緒安全,怎樣才能做到執行緒安全,

這些問題解答后,多執行緒這頭大怪獸自然就會變成溫順的小貓咪,

可上圖關小貓咪屁事!

關你什么屁事

生活中我們口頭上經常說的一句話就是“關你屁事”,大家想一想,為什么我們的屁事不關別人?

原因很簡單,這是我的私事啊!我的衣服、我的電腦,我的手機、我的車子、我的別墅以及私人泳池(可以沒有,但不妨礙想象),我想怎么處理就怎么處理,妨礙不到別人,只屬于我一個人的東西以及事情當然不關別人,即使是屁事也不關別人

我們在自己家里想吃什么吃什么,想去廁所就去廁所!因為這些都是我私有的,只有我自己使用

那么什么時候會和其它人有交集呢?

答案就是公共場所

在公共場所下你不能像在自己家里一樣想去哪就去哪,想什么時候去廁所就去廁所,為什么呢?原因很簡單,因為公共場所下的飯館、衛生間不是你家的,這是公共資源,大家都可以使用的公共資源,

如果你想去飯館、去公共衛生間那么就必須遵守規則,這個規則就是排隊,只有前一個人用完公共資源后下一個人才可以使用,而且不能同時使用,想使用就必須排隊等待

上面這段話道理足夠簡單吧,

如果你能理解這段話,那么馴服多執行緒這頭小怪獸就不在話下,

維護公共場所秩序

如果把你自己理解為執行緒的話,那么在你自己家里使用私有資源就是所謂的執行緒安全,原因很簡單,因為你隨便怎么折騰自己的東西(資源)都不會妨礙到別人

但到公共場所浪的話就不一樣了,在公共場所使用的是公共資源,這時你就不能像在自己家里一樣想怎么用就怎么用想什么時候用就什么時候用,公共場所必須有相應規則,這里的規則通常是排隊,只有這樣公共場所的秩序才不會被破壞,執行緒以某種不妨礙到其它執行緒的秩序使用共享資源就能實作執行緒安全,

因此我們可以看到,這里有兩種情況:

  • 執行緒私有資源,沒有執行緒安全問題

  • 共享資源,執行緒間以某種秩序使用共享資源也能實作執行緒安全,

本文都是圍繞著上述兩個核心點來講解的,現在我們就可以正式的聊聊編程中的執行緒安全了,

什么是執行緒安全

我們說一段代碼是執行緒安全的,當且僅當我們在多個執行緒中同時且多次呼叫的這段代碼都能給出正確的結果,這樣的代碼我們才說是執行緒安全代碼,Thread Safety,否則就不是執行緒安全代碼,thread-unsafe.,

非執行緒安全的代碼其運行結果是由擲骰子決定的,

怎么樣,執行緒安全的定義很簡單吧,也就是說你的代碼不管是在單個執行緒還是多個執行緒中被執行都應該能給出正確的運行結果,這樣的代碼是不會出現多執行緒問題的,就像下面這段代碼:

int func() {
  int a = 1;
  int b = 1;
  return a + b;
}

對于這樣段代碼,無論你用多少執行緒同時呼叫、怎么呼叫、什么時候呼叫都會回傳2,這段代碼就是執行緒安全的,

那么我們該怎樣寫出執行緒安全的代碼呢?要回答這個問題,我們需要知道我們的代碼什么時候呆在自己家里使用私有資源,什么時候去公共場所浪使用公共資源,也就是說你需要識別執行緒的私有資源和共享資源都有哪些,這是解決執行緒安全問題的核心所在,

執行緒私有資源

執行緒運行的本質其實就是函式的執行,函式的執行總會有一個源頭,這個源頭就是所謂的入口函式,CPU從入口函式開始執行從而形成一個執行流,只不過我們人為的給執行流起一個名字,這個名字就叫執行緒,

既然執行緒運行的本質就是函式的執行,那么函式運行時資訊都保存在哪里呢?

答案就是堆疊區,每個執行緒都有一個私有的堆疊區,因此在堆疊上分配的區域變數就是執行緒私有的,無論我們怎樣使用這些區域變數都不管其它執行緒屁事,

執行緒私有的堆疊區就是執行緒自己家

執行緒間共享資料

除了上一節提到的剩下的區域就是公共場合了,這包括:

  • 用于動態分配記憶體的堆區,我們用C/C++中的malloc或者new就是在堆區上申請的記憶體

  • 全域區,這里存放的就是全域變數

  • 檔案,我們知道執行緒是共享行程打開的檔案

要知道這兩個區域是不能被修改的,也就是說這兩個區域是只讀的,因此多個執行緒使用是沒有問題的,

在剛才我們提到的堆區、資料區以及檔案,這些就是所有的執行緒都可以共享的資源,也就是公共場所,執行緒在這些公共場所就不能隨便浪了,

執行緒使用這些共享資源必須要遵守秩序,這個秩序的核心就是對共享資源的使用不能妨礙到其它執行緒,無論你使用各種鎖也好、信號量也罷,其目的都是在維護公共場所的秩序,

知道了哪些是執行緒私有的,哪些是執行緒間共享的,接下來就簡單了,

值得注意的是,關于執行緒安全的一切問題全部圍繞著執行緒私有資料與執行緒共享資料來處理,抓住了執行緒私有資源和共享資源這個主要矛盾也就抓住了解決執行緒安全問題的核心

接下來我們看下在各種情況下該怎樣實作執行緒安全,依然以C/C++代碼為例,但是這里講解的方法適用于任何語言,請放心,這些代碼足夠簡單,

只使用執行緒私有資源

我們來看這段代碼:

int func() {
  int a = 1;
  int b = 1;
  return a + b;
}

這段代碼在前面提到過,無論你在多少個執行緒中怎么呼叫什么時候呼叫,func函式都會確定的回傳2,該函式不依賴任何全域變數,不依賴任何函式引數,且使用的區域變數都是執行緒私有資源,這樣的代碼也被稱為無狀態函式,stateless,很顯然這樣的代碼是執行緒安全的,

這樣的代碼請放心大膽的在多執行緒中使用,不會有任何問題,

有的同學可能會說,那如果我們還是使用執行緒私有資源,但是傳入函式引數呢?

執行緒私有資源+函式引數

這樣的代碼是執行緒安全的嗎?自己先想一想這個問題,答案是it depends,也就是要看情況,看什么情況呢?

1,按值傳參

如果你傳入的引數的方式是按值傳入,那么沒有問題,代碼依然是執行緒安全的:

int func(int num) {
  num++;
  return num;
}

這段代碼無論在多少個執行緒中呼叫怎么呼叫什么時候呼叫都會正確回傳引數加1后的值,原因很簡單,按值傳入的這些引數是執行緒私有資源,

2,按參考傳參

但如果是按參考傳入引數,那么情況就不一樣了:

int func(int* num) {
  ++(*num);
  return *num;
}

如果呼叫該函式的執行緒傳入的引數是執行緒私有資源,那么該函式依然是執行緒安全的,能正確的回傳引數加1后的值,

但如果傳入的引數是全域變數,就像這樣:

int global_num = 1;

int func(int* num) {
  ++(*num);
  return *num;
}

// 執行緒1
void thread1() {
  func(&global_num);
}

// 執行緒2
void thread1() {
  func(&global_num);
}

那此時func函式將不再是執行緒安全代碼,因為傳入的引數指向了全域變數,這個全域變數是所有執行緒可共享資源,這種情況下如果不改變全域變數的使用方式,那么對該全域變數的加1操作必須施加某種秩序,比如加鎖,

有的同學可能會說如果我傳入的不是全域變數的指標(參考)是不是就不會有問題了?

答案依然是it depends,要看情況,

即便我們傳入的引數是在堆上(heap)用malloc或new出來的,依然可能會有問題,為什么?

答案很簡單,因為堆上的資源也是所有執行緒可共享的

假如有兩個執行緒呼叫func函式時傳入的指標(參考)指向了同一個堆上的變數,那么該變數就變成了這兩個執行緒的共享資源,在這種情況下func函式依然不是執行緒安全的,

改進也很簡單,那就是每個執行緒呼叫func函式傳入一個獨屬于該執行緒的資源地址,這樣各個執行緒就不會妨礙到對方了,因此,寫出執行緒安全代碼的一大原則就是能用執行緒私有的資源就用私有資源,執行緒之間盡最大可能不去使用共享資源

如果執行緒不得已要使用全域資源呢?

使用全域資源

使用全域資源就一定不是執行緒安全代碼嗎?

答案還是,,有的同學可能已經猜到了,答案依然是要看情況,

如果使用的全域資源只在程式運行時初始化一次,此后所有代碼對其使用都是只讀的,那么沒有問題,就像這樣:

int global_num = 100; //初始化一次,此后沒有其它代碼修改其值

int func() {
  return global_num;
}

我們看到,即使func函式使用了全域變數,但該全域變數只在運行前初始化一次,此后的代碼都不會對其進行修改,那么func函式依然是執行緒安全的,

但,如果我們簡單修改一下func:

int global_num = 100; 

int func() {
  ++global_num;
  return global_num;
}

這時,func函式就不再是執行緒安全的了,對全域變數的修改必須加鎖保護,

執行緒區域存盤

接下來我們再對上述func函式簡單修改:

__thread int global_num = 100; 

int func() {
  ++global_num;
  return global_num;
}

我們看到全域變數global_num前加了關鍵詞__thread修飾,這時,func代碼就是又是執行緒安全的了,

為什么呢?

其實在上一篇文章中我們講過,被__thread關鍵詞修飾過的變數放在了執行緒私有存盤中,Thread Local Storage,什么意思呢?

意思是說這個變數是執行緒私有的全域變數:

  • global_num是全域變數

  • global_num是執行緒私有的

各個執行緒對global_num的修改不會影響到其它執行緒,因為是執行緒私有資源,因此func函式是執行緒安全的,

說完了區域變數、全域變數、函式引數,那么接下來就到函式回傳值了,

函式回傳值

這里也有兩種情況,一種是函式回傳的是值;另一種回傳對變數的參考,

1,回傳的是值

我們來看這樣一段代碼:

int func() {
  int a = 100;
  return a;
}

毫無疑問,這段代碼是執行緒安全的,無論我們怎樣呼叫該函式都會回傳確定的值100,

2,回傳的是參考

我們把上述代碼簡單的改一改:

int* func() {
  static int a = 100;
  return &a;
}

如果我們在多執行緒中呼叫這樣的函式,那么接下來等著你的可能就是難以除錯的bug以及漫漫的加班長夜,,

很顯然,這不是執行緒安全代碼,產生bug的原因也很簡單,你在使用該變數前其值可能已經被其它執行緒修改了,因為該函式使用了一個靜態全域變數,只要能拿到該變數的地址那么所有執行緒都可以修改該變數的值,因為這是執行緒間的共享資源,不到萬不得已不要寫出上述代碼,除非老板拿刀架在你脖子上,

但是,請注意,有一個特例,這種使用方法可以用來實作設計模式中的單例模式,就像這樣:

class S {
public:
  static S& getInstance() {
    static S instance;
    return instance;
  }
private:
  S() {}
  
// 其它省略
}

為什么呢?

因為無論我們呼叫多少次func函式,static區域變數都只會被初始化一次,這種特性可以很方便的讓我們實作單例模式,

最后讓我們來看下這種情況,那就是如果我們呼叫一個非執行緒安全的函式,那么我們的函式是執行緒安全的嗎?

呼叫非執行緒安全代碼

假如一個函式A呼叫另一個函式B,但B不是執行緒安全,那么函式A是執行緒安全的嗎?

答案依然是,要看情況,

我們看下這樣一段代碼,這段代碼在之前講解過:

int global_num = 0;

int func() {
  ++global_num;
  return global_num;
}

我們認為func函式是非執行緒安全的,因為func函式使用了全域變數并對其進行了修改,但如果我們這樣呼叫func函式:

int funcA() {
  mutex l;
   
  l.lock();
  func();
  l.unlock();
}

雖然func函式是非執行緒安全的,但是我們在呼叫該函式前加了一把鎖進行保護,那么這時funcA函式就是執行緒安全的了,其本質就是我們用一把鎖間接的保護了全域變數,

再看這樣一段代碼:

int func(int *num) {
  ++(*num);
  return *num;
}

一般我們認為func函式是非執行緒安全的,因為我們不知道傳入的指標是不是指向了一個全域變數,但如果呼叫func函式的代碼是這樣的:

void funcA() {
  int a = 100;
  func(&a);
}

那么這時funcA函式依然是執行緒安全的,因為傳入的引數是執行緒私有的區域變數,無論多少執行緒呼叫funcA都不會干擾到其它執行緒,

看了各種情況下的執行緒安全問題,最后讓我們來總結一下實作執行緒安全代碼都有哪些措施,

如何實作執行緒安全

從上面各種情況的分析來看,實作執行緒安全無外乎圍繞執行緒私有資源和執行緒共享資源這兩點,你需要識別出哪些是執行緒私有,哪些是共享的,這是核心,然后對癥下藥就可以了,

  • 不使用任何全域資源,只使用執行緒私有資源,這種通常被稱為無狀態代碼
  • 執行緒區域存盤,如果要使用全域資源,是否可以宣告為執行緒區域存盤,因為這種變數雖然是全域的,但每個執行緒都有一個屬于自己的副本,對其修改不會影響到其它執行緒
  • 只讀,如果必須使用全域資源,那么全域資源是否可以是只讀的,多執行緒使用只讀的全域資源不會有執行緒安全問題,
  • 原子操作,原子操作是說其在執行程序中是不可能被其它執行緒打斷的,像C++中的std::atomic修飾過的變數,對這類變數的操作無需傳統的加鎖保護,因為C++會確保在變數的修改程序中不會被打斷,我們常說的各種無鎖資料結構通常是在這類原子操作的基礎上構建的
  • 同步互斥,到這里也就確定了你必須要以某種形式使用全域資源,那么在這種情況下公共場所的秩序必須得到維護,那么怎么維護呢?通過同步或者互斥的方式.

總結

怎么樣,想寫出執行緒安全的還是不簡單的吧,如果本文你只能記住一句話的話,那么我希望是這句,這也是本文的核心:

實作執行緒安全無外乎圍繞執行緒私有資源和執行緒共享資源來進行,你需要識別出哪些是執行緒私有,哪些是共享的,然后對癥下藥就可以了

希望本文對大家撰寫多執行緒程式有幫助,

寫在最后

歡迎大家關注我的公眾號【風平浪靜如碼】,海量Java相關文章,學習資料都會在里面更新,整理的資料也會放在里面,

覺得寫的還不錯的就點個贊,加個關注唄!點關注,不迷路,持續更新!!!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/242193.html

標籤:Java

上一篇:delete、truncate、drop,千萬別用錯了。。

下一篇:Java 查找并替換PDF中的指定文本

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more