單核cpu多線程有必要嗎?


問題分析

現代計算機一般都是多核cpu,多線程的可以大大提高效率,但是可能會有疑問,那單核CPU使用多線程是不是沒有必要了,假定一種情況,web應用服務器,單核CPU、單線程,用戶發過來請求,單個線程處理,CPU等待這個線程的處理結果返回,查詢數據庫,CPU等待查詢結果...,只有一個線程的話,每次線程在處理的過程中CPU都有大量的空閑等待時間,那這樣來說並行和串行似乎並沒有體現並行的優勢,因為任務的總量在那里,實際情況肯定不是這樣的,即便是單核CPU,一個進程中往往也是有多個線程存在的,每個線程各司其職,CPU來調度各線程。

這里需要區分CPU處理指令和IO讀取的不同,CPU的執行速度要遠大於IO的過程,因此在大多數情況下多一些復雜的CPU計算都比增加一次IO要快,這一塊深入理解要學習計算機原理相關的知識。

現實生活中也是有很多類似的例子,比如廚師做一道菜,買菜和買配料需要去不同的兩個商店,如果這個過程只依靠他一個人來做,那耗費的總時間就是買菜再去買調料的總時間,如果有一個幫廚,那么就可以兵分兩路,再來匯總結果,時間基本可以減半,廚師和幫廚就是不同的線程。

編程是高度抽象生活的一門藝術。

場景模擬

模擬單線程和多線程的效率差距,這里使用連接數據庫,和讀取磁盤文件來模擬IO操作,期望結果:

單線程總耗時:數據庫連接耗時 + 磁盤文件讀取耗時
多線程總耗時:約等於耗時最長的那個時間

讀取文件:https://gitee.com/chsoul/javase/blob/master/medias/testIO.txt

MySQL8.0 連接驅動:https://gitee.com/chsoul/javase/blob/master/medias/mysql-connector-java-8.0.18.jar

代碼如下:

package com.thread.demo;

import java.io.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @author Vicente
 * @version 1.0
 * @date 2020/4/6 21:53
 */
public class Test {
    /**
     * 數據庫連接
     */
    public static void getMysqlData(){
        long t1 = System.currentTimeMillis();
        String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true";
        String userName = "root";
        String password = "root";
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection = DriverManager.getConnection(url, userName, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("數據庫連接完成!耗時:"+(t2 - t1));
    }

    /**
     * 磁盤讀取
     */
    public static void getDiskData(){
        long t1 = System.currentTimeMillis();
        File file = new File("src/com/thread/demo/test.txt");
        StringBuilder stb = new StringBuilder();
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            int read = 0;
            while ((read = is.read()) != -1) {
                char c = (char) read;
                stb.append(c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("磁盤文件讀取結束!耗時:"+(t2 - t1));
    }

    public static void main(String[] args){
        System.out.println("-----------------單線程執行任務開始-----------------");
        long start = System.currentTimeMillis();
        getMysqlData();
        getDiskData();
        long end = System.currentTimeMillis();
        System.out.println("總耗時:"+(end - start));
        System.out.println("-----------------單線程執行任務結束-----------------");
        System.out.println("\r\n");
        try {
            System.out.println("-----------------多線程執行任務開始-----------------");
            long start1 = System.currentTimeMillis();
            FutureTask dbWork = new FutureTask(new DbDataWork());
            FutureTask diskWork = new FutureTask(new DiskDataWork());
            new Thread(dbWork).start();
            new Thread(diskWork).start();
            while (diskWork.get().equals("DISK_OK") && dbWork.get().equals("DB_OK")){
                long end1 = System.currentTimeMillis();
                System.out.println("總耗時:"+(end1 - start1));
                System.out.println("-----------------多線程執行任務結束-----------------");
                break;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 數據庫連接任務類
 */
class DbDataWork implements Callable<String> {
    @Override
    public String call() throws Exception {
        long t1 = System.currentTimeMillis();
        String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true";
        String userName = "root";
        String password = "root";
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
            Connection connection = DriverManager.getConnection(url, userName, password);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("數據庫連接線程任務結束!耗時:"+(t2 - t1));
        return "DB_OK";
    }
}

/**
 * 磁盤讀取任務類
 */
class DiskDataWork implements Callable<String>{
    @Override
    public String call() throws Exception {
        long t1 = System.currentTimeMillis();
        File file = new File("src/com/thread/demo/test.txt");
        StringBuilder stb = new StringBuilder();
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            int read = 0;
            while ((read = is.read()) != -1) {
                char c = (char) read;
                stb.append(c);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long t2 = System.currentTimeMillis();
        System.out.println("磁盤讀取線程任務結束!耗時:"+(t2 - t1));
        return "DISK_OK";
    }
}

執行結果:

-----------------單線程執行任務開始-----------------
數據庫連接完成!耗時:694
磁盤文件讀取結束!耗時:558
總耗時:1253
-----------------單線程執行任務結束-----------------

-----------------多線程執行任務開始-----------------
數據庫連接線程任務結束!耗時:743
磁盤讀取任務結束!耗時:752
總耗時:755
-----------------多線程執行任務結束-----------------

總結

結果符合預期,這也說明在有頻繁的IO操作時使用多線程會大大提高程序的執行效率。
有興趣的同學可以試一下在執行i++的情況下,多線程就一定快嗎?單線程和多線程的臨界值是什么?

附:

  • 為什么Redis單線程卻很快,在沒有磁盤IO的情況下單核CPU綁定一塊內存效率最高,Redis把讀寫操作都放在了CPU和內存的部分,又減少了多線程上下文 切換的過程,因此Redis即便是單線程也很快,在現代多核CPU的服務器中,往往會通過根據綁定Redis進程和CPU提高性能。
  • 《性能調優攻略》在多核CPU調優章節提到,我們不能任由操作系統負載均衡,因為我們自己更了解自己的程序,所以,我們可以手動地為其分配CPU核,而不會過多地占用CPU0,或是讓我們關鍵進程和一堆別的進程擠在一起。在文章中提到了Linux下的一個工具,taskset,可以設定單個進程運行的CPU。
    詳細可參考:https://www.cnblogs.com/blogtech/p/11742057.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM