關於Netty併發安全代碼組織發揮多核CPU優勢的思考及其測試(以房間服務器爲例子)

package com.mmall.mytest;

import java.util.Random;

class Num {
    private Random r = new Random();

    private int num = 5000;

    // 模擬一個業務,比如: 吃碰槓
    public void add() {
        synchronized (this) {

            if (num > 5000) {
                System.out.println("不論到你出牌");
                return;
            }

            try {
                System.out.println(Thread.currentThread().getName() + ">>>收到加法指令,進行一些操作");
                Thread.sleep(r.nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num++;

            System.out.println(Thread.currentThread().getName() + ">>>加法操作後num=" + this.num + "\n");
        }
    }

    // 模擬另外一個業務。 同時和上面的業務有互斥關係。 用同步代碼塊模擬一個房間一次只能處理一條指令
    public void sub() {
        synchronized (this) {
            if (num < 5000) {
                System.out.println("不論到你出牌");
                return;
            }

            try {
                System.out.println(Thread.currentThread().getName() + "<<<收到減法指令,進行一些操作");
                Thread.sleep(r.nextInt(10));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "<<<減法操作後num=" + this.num + "\n");
        }
    }

    @Override
    public String toString() {
        return "Num{" +
                "num=" + num +
                '}';
    }
}

public class Main {
    public static void main(String[] args) {
        Random r = new Random();

        Num num = new Num();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    try {
                        Thread.sleep(r.nextInt(10));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    num.add();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(r.nextInt(10));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 1000; i++) {
                    num.sub();
                }
            }
        }).start();
    }
}

不論到你出牌
不論到你出牌
不論到你出牌
不論到你出牌
不論到你出牌
不論到你出牌
Thread-12>>>收到加法指令,進行一些操作
Thread-12>>>加法操作後num=5000

Thread-10>>>收到加法指令,進行一些操作
Thread-10>>>加法操作後num=5001

不論到你出牌
Thread-11<<<收到減法指令,進行一些操作
Thread-11<<<減法操作後num=5000

Thread-11<<<收到減法指令,進行一些操作
Thread-11<<<減法操作後num=4999

Thread-16>>>收到加法指令,進行一些操作
Thread-16>>>加法操作後num=5000

Thread-14>>>收到加法指令,進行一些操作
Thread-14>>>加法操作後num=5001

。。。。

 

總結:

以上簡單模擬了房間出牌的過程,可以看出,netty實現房間類的服務器還是非常簡單的,根本不需要用什麼高級的做法,只需要做到2點即可:

(1)所有的房間對象的管理存儲到線程安全的ConcurrentHashMap中

HashMap<Integer, Room> roomMgr = new ConcurrentHashMap();

 

(2)在收到每個玩家的指令時,在處理玩家邏輯時,只需要加同步synchronized (this), 當然this就是房間Room對象,這樣每個房間保證一次只能處理一條玩家的指令。 也就是: 在吃、碰、槓、過。。。等這些用同步代碼包住。 這樣就防止了服務端2個人同時併發的操作的問題。 由於一個房間通常只有4個人,因此基本不會有鎖的爭奪。

只所以加鎖是因爲服務器不應該相信客戶端,當客戶端不論到他出牌時,他卻想出牌。這樣給客戶端返回錯誤使用。

同時在一條指令執行時,另外一個人的指令阻止下來不讓它操作。上一個指令計算完成,才允許處理下一條指令,保證邏輯安全性。

 

我的思考:

1)這樣由於每個房間單元就是獨立的。 每條操作房間的指令就可以都封裝成Task,然後被丟到業務線程池中併發的調度。發揮Netty巨大的優勢!!!

而不是像Node.js這種,假如1000個房間落在了A進程。 1000個房間落在了B進程...1000個房間落在了C進程..1000個房間落在了D進程..  當進程A中的玩家比較活躍時,卻只能利用一個CPU核心進行調度,這其實並沒有發揮多核心的優勢!

2)上述簡單加鎖的方式。 1000個房間,則開多少個業務線程,這些房間在計算時,就真正的多個條線並行的執行!充分發揮多核CPU的優勢。

3)當然了,CPU計算型的業務線程其實不應該開多個。 適當少開一些,甚至是CPU核心+1即可。

4)IO密集型的線程池由於大部分都是等待操作,線程數可以開多一點,但是爲了簡單起見。 比如:開100個線程用於IO + 業務處理,也足夠了! 32個核心的CPU機器,足夠應對大部分遊戲場景了。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章