常用語言的線程模型(Java、go、C++、python3)

来源:https://www.cnblogs.com/Jcloud/archive/2023/07/17/17559171.html
-Advertisement-
Play Games

瞭解一下線程模型還是很有必要的,如果不清楚語言層面上的線程在操作系統層面怎麼映射使用,在使用過程中就會不清不楚,可能會踩一些坑 ...


背景知識

  1. 軟體是如何驅動硬體的?
    硬體是需要相關的驅動程式才能執行,而驅動程式是安裝在操作系統內核中。如果寫了一個程式A,A程式想操作硬體工作,首先需要進行系統調用,由內核去找對應的驅動程式驅使硬體工作。而驅動程式怎麼讓硬體工作的呢?驅動程式作為硬體和操作系統之間的媒介,可以把操作系統中相關的指令翻譯成硬體能夠識別的電信號,同時,驅動程式也可以將硬體的電信號轉為操作系統能夠識別的指令。
  2. 進程、輕量級進程、線程關係
    一個進程由於所運行的空間不同,被分為內核線程和用戶進程,之所有稱之為內核線程,是因為其不擁有虛擬地址空間。如果創建一個新的用戶進程,會分配一個新的虛擬地址空間,不同用戶進程之間資源是隔離的。由於創建一個新的進程需要消耗很多的資源,並且在進程之間切換的代價也很昂貴,因此引入了輕量級進程。輕量級進行本質上也是對內核線程的高層抽象,雖然不同的輕量級進程之間可以共用某些資源,但由於輕量級進程本質上還是內核線程,如果進行輕量級線程之間的切換,需要進行系統調用,代價也是比較昂貴的。內核本質上只能感知到進程的存在,像不同語言的多線程技術,是在用戶進程的基礎上創建的線程庫,線程本身不參與處理器競爭,而是由其所屬的用戶進程參與處理器的競爭。
  3. 如何理解用戶態和內核態
    首先我們需要理解到電腦資源是有限的,不管是CPU資源、記憶體資源、IO資源、網路資源,為了保證這些資源的合理利用,需要有一個管控機制,而這個管控機制都是交於操作系統來處理的。用戶態和內核態是操作系統的一種邏輯劃分,本質上是進行許可權控制,處於用戶態的進程可以直接使用分配給其的記憶體空間,但如果想使用CPU等稀缺資源,處於用戶態的進程就沒有這個許可權了,必須通過系統調用,讓當前進程進入內核態,這樣可以有更大的許可權去申請CPU資源、記憶體資源、IO資源等;

操作系統線程模型

java語言

線程模型

在Java誕生之初,在Java中就引入了線程,最初稱之為“綠色線程”,完全由JVM進行管理,這和操作系統用戶線程是多對一的實現,但隨著操作系統對線程支持越來越強大,java中的線程實現採用了一對一的實現,即一個java線程對應於一個操作系統用戶線程,但是這個線程的堆棧大小是固定的,隨著線程數量創建過多,可能導致記憶體溢出。在java19版本中引入了虛擬線程的概念,虛擬線程有一個動態的堆棧,可以增大和縮小,這和操作系統用戶線程之間是一個多對多的關係,隨著後面的發展,java中的線程模型會變得越來越強大。

優缺點

作為一對一的線程模型維護起來比較簡單,但是由於每一個線程棧信息是固定的,不利於創建大量的線程,並且多線程操作時可能涉及頻繁的系統調用,上下文切換代價高。

使用方式(以生產者消費者模型來說明)

 public class ThreadTest {

    public static final Object P = new Object();

    static List<Integer> list = new ArrayList<>();

    @Test
    public void test() throws Exception {

        Thread thread1 = new Thread(()-> {
            while(true) {
                try {
                    product();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            while(true) {
                try {
                    consume();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }

    private static void product() throws Exception {
        synchronized (P) {
            if(list.size() == 1) {
                // 讓出鎖
                P.wait();
            }
            list.add(1);
            System.out.println("produce");
            P.notify();
        }
    }

    private static void consume() throws Exception {
        synchronized (P) {
            if(list.size() == 0) {
                P.wait();
            }
            list.remove(list.size() - 1);
            System.out.println("consume");
            P.notify();
        }
    }
}

go語言

go語言線程模型

在go語言中,線程模型就是比較強大了,包含了三個概念:內核線程(M)、goroutine(G)、G的上下文環境(P)。其中G表示基於協程創建的用戶線程,M直接關聯一個內核線程,P裡面一般存放正在運行的goroutine的上下文環境(函數指針、堆棧地址和地址邊界等)。

優缺點

go語言中的線程模型算是很強大了,引用了協程,線程棧大小可以動態調整,很好地避免了java中目前的線程模型缺點。

使用方式(以生產者消費者模型來說明)

package main

import (
	"fmt"
)

type ThreadTest struct {
	lock chan int
}

func (t *ThreadTest) produce() {
	for {
		t.lock <- 10
		fmt.Println("produce:", 10)
	}
}

func (t *ThreadTest) consume() {
	for {
		v := <-t.lock
		fmt.Println("consume:", v)
	}
}

func main() {
	maxLen := 10
	t := &ThreadTest{
		make(chan int, maxLen),
	}
	// 重點在這裡,開啟新的協程,配合通道,讓go的多線程變成非常優雅
	go t.consume()
	go t.produce()
	select {}

}
 

c++語言

c++語言線程模型

在c++11中增加了操作thread庫,提供對線程操作的進一步封裝,而這個庫底層是使用了pthread庫,這個庫底層採用了1:1線程模型,跟java中的線程模型類似。

優缺點

作為一對一的線程模型維護起來比較簡單,但是由於每一個線程棧信息是固定的,不利於創建大量的線程,並且多線程操作時可能涉及頻繁的系統調用,上下文切換代價高。

使用方式(以生產者消費者模型來說明)

#include 
#include 
#include 
#include  

static const int SIZE = 10;
static const int ITEM_SIZE = 30;

std::mutex mtx;

std::condition_variable not_full;
std::condition_variable not_empty;

int items[SIZE];

static std::size_t r_idx = 0;
static std::size_t w_idx = 0;

void produce(int i) {
    std::unique_lock lck(mtx);
    while((w_idx+ 1) % SIZE == r_idx) {
        std::cout << "隊列滿了" << std::endl;
        not_full.wait(lck);
    }
    items[w_idx] = i;
    w_idx = (w_idx+ 1) % SIZE;
    not_empty.notify_all();
    lck.unlock();
}

int consume() {
    int data;
    std::unique_lock lck(mtx);
    while(w_idx == r_idx) {
        std::cout << "隊列為空" << std::endl;
        not_empty.wait(lck);
    }
    data = items[r_idx];
    r_idx = (r_idx + 1) % SIZE;
    not_full.notify_all();
    lck.unlock();
    return data;
}

void p_t() {
    for(int i = 0; i < ITEM_SIZE; i++) {
        produce(i);
    }
}

void c_t() {
    static int cnt = 0;
    while(1) {
        int item = consume();
        std::cout << "消費第" << item << "個商品" << std::endl;
        if(++cnt == ITEM_SIZE) {
            break;
        }
    }
}

int main() {
    std::thread producer(p_t);
    std::thread consumer(c_t);
    producer.join();
    consumer.join();
}

python語言

python線程模型

python中的線程使用了操作系統的原生線程,python虛擬機使用了一個全局互斥鎖(GIL)來互斥線程對Python虛擬機的使用,當一個線程獲取GIL的許可權之後,其他的線程必須等待這個線程釋放GIL鎖,索引再多核CPU上,python多線程也會退化為單線程,無法利用多核的優勢。

優缺點

python語言多線程由於GIL的存在,在計算密集型場景上,很難體現到優勢,並且由於涉及線程切換的代碼,反而可能性能還不如單線程好。

使用方式(以生產者消費者模型來說明)

#! /usr/bin/python3

import threading
import random
import time

total = 100
lock = threading.Lock()
totalTime = 10
gTime = 0

class Consumer(threading.Thread):
        def run(self):
                global total
                global gTime
                while True:
                        cur = random.randint(10, 100)
                        lock.acquire()
                        if total >= cur:
                                total -= cur
                                print("{}使用了{}, 當前剩餘{}".format(threading.current_thread(), cur, total))
                        else:
                            print("{}準備使用{},當前剩餘{},不足,不能消費".format(threading.current_thread(), cur, total))
                        if gTime == totalTime:
                               lock.release()
                               break
                        lock.release()
                        time.sleep(0.7)

class Producer(threading.Thread):
    def run(self):
           global total
           global gTime
           while True:
                  cur = random.randint(10, 100)
                  lock.acquire()
                  if gTime == totalTime:
                         lock.release()
                         break
                  total += cur
                  print("{}生產了{}, 剩餘{}".format(threading.current_thread(), cur, total))
                  gTime+= 1
                  lock.release()
                  time.sleep(0.5)
if __name__ == '__main__':
       t1 = Producer(name="生產者")
       t1.start()
       t2 = Consumer(name="消費者")
       t2.start()

總結

在目前的線程模型中,有1:1、M:1、M:N多種線程模型,具體採用哪種線程模型也和硬體和操作系統的支持程度有關,像誕生比較早的語言,普通採用M:1、1:1線程模型,像c++、java。而新誕生不久的go語言,採用的是M:N線程模型,在多線程的支持上更加強大。

感覺瞭解一下線程模型還是很有必要的,如果不清楚語言層面上的線程在操作系統層面怎麼映射使用,在使用過程中就會不清不楚,可能會踩一些坑,我們都知道在java中不同無限的創建線程,這會導致記憶體溢出,go語言中對多線程支持更加強大,很多事情不需要我們再去關註了,在語言底層已經幫助我們做了。

每種語言的底層細節太多了,如果想深入研究某一個技術,還是得花精力去研究。

作者:京東零售 薑昌偉

來源:京東雲開發者社區


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • # 4. 列表 列表非常適合於存儲程式運行期間可能變化的數據集。 ## 遍歷列表 ```py nums = ["alice","david","carolina"] for iter in nums: print(iter) ``` ## 創建數值列表 1、簡單使用range() 函數 ```py ...
  • - 準備環境 1. python3.7+ 2. 依賴:aiohttp - 代碼實現(代理伺服器,返迴響應體和進行跨域處理後的headers) ``` python3.7 import aiohttp from functools import wraps from aiohttp import we ...
  • CheckStyle作為檢驗代碼規範的插件,除了可以使用配置預設給定的開發規範,如Sun的,Google的開發規範啊,也可以導入像阿裡的開發規範的插件。 事實上,每一個公司都存在不同的開發規範要求,所以大部分公司會給定自己的check規範,一般導入給定的 checkstyle.xml 文件即可實現。 ...
  • # AbstractJsonUserAttributeMapper 它是一個抽象類,用來更新條件更新用戶屬性(user_attribute)的信息,我們在實現自己的mapper時,需要關註3個方法,下麵分別介紹一下: ## getCompatibleProviders方法 它用來直指你的mapper ...
  • # 構造方法參數Autowire - BeanClass可以在構造方法上標註@Autowired註解,Spring在創建Bean實例時將自動為其註入依賴參數 - Spring會優先使用標註@Autowired註解的構造方法 - 當一個構造方法標註了@Autowired註解且required=true ...
  • 在 Maven 中,`-SNAPSHOT` 尾碼是用於標識項目版本為快照(Snapshot)版本的約定。快照版本是處於開發和演進中的版本,通常用於開發人員在`構建和測試過程中進行頻繁的版本迭代`;反之,如果不是Snapshot尾碼的包,例如v1.1.0,這說明它是一個相對穩定的版本了,這個版本一經發 ...
  • ## 教程簡介 JFreeChart是JAVA平臺上的一個開放的圖表繪製類庫。它完全使用JAVA語言編寫,是為applications, applets, servlets 以及JSP等使用所設計。JFreeChart可生成餅圖(pie charts)、柱狀圖(bar charts)、散點圖(sca ...
  • ## 教程簡介 cPanel虛擬主機管理系統提供一個簡單但卻非常直觀的圖形化界面來幫助伺服器管理人員管理伺服器。伺服器管理人員可以通過cPanel虛擬主機管理系統的EasyApache功能來進行Apache的編譯,輕鬆選擇想要載入的模塊,整個編譯過程十分簡單,從而避免了從命令行來進行編譯的繁瑣。 c ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...