PHP là đơn luồng hoặc đa luồng

Trong vài tuần qua, tôi đã gặp phải các vấn đề về đồng thời và các cuộc thảo luận ở nhiều nơi khác nhau. Tôi hiểu các nguyên tắc cơ bản của đồng thời, nhưng tôi chưa có nhiều kinh nghiệm thực hành như mong muốn, vì vậy tôi quyết định tìm hiểu bằng cách chạy một số thử nghiệm và viết về chúng

Thí nghiệm 1. Máy chủ tích hợp của PHP không có đồng thời

PHP đi kèm với một máy chủ tích hợp. php -S localhost:8000 -t myapp/ sẽ bắt đầu một máy chủ đơn giản cho ứng dụng của bạn. Một số khung cung cấp một trình bao bọc xung quanh điều này [

const requestHandler = function [req, res] {
    console.time[req.url];
    console.log[`Starting: ${req.url}`];
    let i = 0;
    while [i < 100_000] {
        const data = fs.readFileSync[__filename];
        i++;
    }
    res.writeHead[200];
    res.end['Hello, World!'];
    console.log[`Finished: ${req.url}`];
    console.timeEnd[req.url];
}

const server = http.createServer[requestHandler];
server.listen[3040];
0]. Nhưng tất cả đều gọi nó là máy chủ phát triển, nghĩa là bạn không nên sử dụng nó trong sản xuất. Một lý do cho điều này là nó mặc định là một luồng. Nó chạy như một luồng duy nhất, vì vậy mỗi yêu cầu được phục vụ bởi cùng một luồng. Và vì PHP là đồng bộ, điều đó có nghĩa là mỗi yêu cầu phải đợi yêu cầu trước đó kết thúc

Tôi đã biết điều này từ lâu, nhưng tôi muốn chứng minh điều đó với chính mình, chỉ để cho vui thôi. Vì vậy, tôi đã làm một bài kiểm tra nhanh

Tôi đã tạo một phần mềm trung gian toàn cầu chạy trên mọi yêu cầu và ghi nhật ký bắt đầu và kết thúc yêu cầu

class Check
{
    public function handle[Request $request, Closure $next]
    {
        ray["Starting: ". $request->fullUrl[]];
        sleep[10];        
        $response = $next[$request];
        ray["Finished: ". $request->fullUrl[]]->green[];
        return $response;
    }
}

Sau đó, tôi đã mở một số tab trình duyệt và tải lại chúng đồng thời. Chắc chắn rồi, mỗi yêu cầu ghi lại "Đang bắt đầu" và "Đã hoàn thành" cách nhau 10 giây, lần lượt từng yêu cầu. Không bao giờ có bất kỳ yêu cầu chồng chéo

chào. Hãy tưởng tượng đây sẽ là vấn đề gì trong quá trình sản xuất. Mỗi người dùng sẽ phải thay phiên nhau truy cập trang web, vì nó không thể xử lý nhiều yêu cầu cùng một lúc

Tại sao lại thế này?

Lý do cho điều này là mô hình thực thi đồng bộ của PHP. PHP có nghĩa là chết. Mô hình thực thi của PHP rất đơn giản. khởi động, thực hiện các lệnh của bạn, sau đó thoát. Không có vòng lặp sự kiện, luồng công nhân hoặc coroutine nào để lo lắng về. Mọi thứ thực hiện lần lượt. Đây là một trong những lý do tại sao nó là một ngôn ngữ đơn giản để quấn lấy đầu bạn

Nhưng khi bạn kết hợp sự thiếu đồng bộ này với một máy chủ đơn luồng, bạn sẽ nhận được yêu cầu xử lý không "mở rộng"

Qua một bên. PHP7. 4 đã giới thiệu biến

const requestHandler = function [req, res] {
    console.time[req.url];
    console.log[`Starting: ${req.url}`];
    let i = 0;
    while [i < 100_000] {
        const data = fs.readFileSync[__filename];
        i++;
    }
    res.writeHead[200];
    res.end['Hello, World!'];
    console.log[`Finished: ${req.url}`];
    console.timeEnd[req.url];
}

const server = http.createServer[requestHandler];
server.listen[3040];
0 thử nghiệm cho phép máy chủ xử lý các yêu cầu đồng thời, nhưng điều đó nằm ngoài phạm vi của chúng tôi ngay bây giờ

Làm thế nào về nút. js?

Nút. js cũng chạy như một máy chủ đơn luồng. Nhưng nút. js đi kèm với tính không đồng bộ sẵn có và I/O hướng sự kiện. Vì vậy, nó vẫn có vấn đề này? . có và không

Trước hết, trình xử lý yêu cầu trong Node. js thực sự được gọi đồng bộ [lần lượt từng cái]. Điều này rất quan trọng, bởi vì rất nhiều người cho rằng các sự kiện được kích hoạt trong Node. js được xử lý không đồng bộ. Họ không. nút. máy chủ js sử dụng một vòng lặp tương tự như máy chủ PHP ở trên. chọn một yêu cầu, thực thi trình xử lý của nó, chuyển sang yêu cầu tiếp theo

Tôi đã chạy một thử nghiệm khác để xác minh điều này

const requestHandler = function [req, res] {
    console.time[req.url];
    console.log[`Starting: ${req.url}`];
    let i = 0;
    while [i < 100_000] {
        const data = fs.readFileSync[__filename];
        i++;
    }
    res.writeHead[200];
    res.end['Hello, World!'];
    console.log[`Finished: ${req.url}`];
    console.timeEnd[req.url];
}

const server = http.createServer[requestHandler];
server.listen[3040];

Mã này hoàn toàn đồng bộ, giống như với PHP. Tôi đang đọc từ một tệp 100.000 lần, chỉ để tạo một số tác phẩm giả cho máy chủ để thực hiện điều đó cần có thời gian

Tiếp theo, tôi đã thực hiện ba yêu cầu cùng một lúc và

Starting: /
Finished: /
/: 4.414s
Starting: /ping
Finished: /ping
/ping: 4.269s
Starting: /dfd
Finished: /dfd
/dfd: 4.281s

Vâng, bạn biết gì không? . Mỗi yêu cầu phải đợi yêu cầu đầu tiên kết thúc trước khi có thể tham gia

Hãy thay đổi mọi thứ một chút

const readFileManyTimes = [i, callback] => {
    fs.readFile[__filename, [err, data] => {
        if [i < 100_000] {
            readFileManyTimes[++i, callback];
        } else {
            callback[];
        }
    }];
}
const requestHandler = function [req, res] {
    console.time[req.url];
    console.log[`Starting: ${req.url}`];
    readFileManyTimes[0, [] => {
        res.writeHead[200];
        res.end['Hello, World!'];
        console.log[`Finished: ${req.url}`];
        console.timeEnd[req.url];
    }];
}

Chúng tôi vẫn đang đọc tệp 100.000 lần trước khi gửi phản hồi, nhưng hiện tại chúng tôi đang thực hiện việc này không đồng bộ

Lần này, kết quả đã khác. Các yêu cầu được xử lý đồng thời

Starting: /
Starting: /ping
Starting: /dfd
Finished: /
/: 11.415s
Finished: /ping
/ping: 11.415s
Finished: /dfd
/dfd: 11.420s

Nếu bạn quen thuộc với đồng thời, những kết quả này sẽ không quá ngạc nhiên. Đồng thời không có nghĩa là thực hiện nhiều nhiệm vụ song song. Thay vào đó, nó liên quan đến một số chiến lược cho phép bạn chuyển đổi giữa các nhiệm vụ đó một cách thông minh để có vẻ như bạn đang xử lý chúng cùng một lúc. Lợi ích chính của đồng thời là không để bất kỳ người nào chờ đợi quá lâu

Đối với những người không hiểu rõ, nó có thể trông giống như Nút. js đang thực hiện cả ba yêu cầu cùng một lúc, nhưng thực tế không phải vậy. Điểm khác biệt chính là tệp không đồng bộ được đọc, cho phép Node. js xử lý để tắt nó cho hệ điều hành và chuyển sang tác vụ khác

So, no. Nút. js sẽ không có giới hạn đồng thời giống như PHP đối với hầu hết các ứng dụng web, vì hầu hết các hoạt động liên kết I/O của nó đều không đồng bộ theo mặc định, nhưng nếu bạn viết mã đồng bộ, bạn sẽ nhận được kết quả đồng bộ. Trong ví dụ đầu tiên, việc chúng tôi sử dụng các lần đọc tệp đồng bộ đã biến tính đồng thời của chúng tôi thành 0

Biết điều này có thể giúp bạn tránh được một số lỗi khó chịu và sự cố gỡ lỗi. Ví dụ: người dùng của bạn có thể phàn nàn rằng trang web của bạn chậm, nhưng khi bạn thêm một số mã để đo thời gian phản hồi, nó sẽ cho bạn biết các yêu cầu đang được xử lý trong một giây hoặc ít hơn. Vì mỗi yêu cầu chỉ được xử lý sau yêu cầu trước đó, nếu mã của bạn là đồng bộ, không có cách nào để ghi lại thời gian chính xác mà yêu cầu đã thực sự được gửi. Một giải pháp cho vấn đề đó là theo dõi thời gian phản hồi của bạn từ bên ngoài, không phải bên trong ứng dụng của bạn

Đồng thời so với độ trễ

Đây là một chút chuyển hướng so với ý định ban đầu của tôi, nhưng một điều thú vị cần xem xét ở đây là sự đánh đổi giữa đồng thời và độ trễ. Trong ví dụ đồng bộ, mỗi yêu cầu được xử lý trong khoảng 4 giây

Trong phiên bản đồng thời, mỗi yêu cầu mất khoảng 11. 4s. Đó là nhiều hơn nữa. Nhưng có một yếu tố quan trọng. thời gian chờ đợi. Trong phiên bản đồng bộ, mỗi yêu cầu phải đợi các yêu cầu trước đó trước khi có thể xử lý. Vì vậy, mặc dù nó được xử lý chỉ trong vài giây từ phía chúng tôi, nhưng người dùng có thể đã đợi một phút hoặc lâu hơn. Để có được thời gian phản hồi thực sự từ phía người dùng, chúng tôi cần thêm thời gian chờ đợi các yêu cầu trước đó

Yêu cầu được xử lý trongThời gian chờThời gian phản hồi thực tế/4. 4s04. 4s/ping4. 3s4. 4s8. 7s/dfd4. 3s8. 7s13s

Vì vậy, một người không may mắn đã phải đợi 13 giây. Sự khác biệt giữa các phiên bản đồng thời và đồng bộ ở đây không nhiều vì chúng tôi chỉ thử nghiệm với 3 yêu cầu đồng thời. Hãy tưởng tượng nếu chúng tôi có 50 yêu cầu cùng một lúc, với mỗi yêu cầu mất 4 giây. Điều đó có nghĩa là chúng tôi sẽ có thời gian phản hồi lớn bằng [4 * 50] = 200 giây. Tôi đã thử nghiệm điều này với autocannon

❯ autocannon --connections 50 --amount 50 --timeout 10000 //localhost:3040
Running 50 requests test @ //localhost:3040
50 connections

50 requests in 208.69s, 7.7 kB read

Chắc chắn rồi, yêu cầu cuối cùng được xử lý trong 4 giây, nhưng phải mất 208 giây kể từ khi yêu cầu được gửi đến khi nó được xử lý lần cuối

Đây là những gì xảy ra với phiên bản đồng thời

❯ autocannon --connections 50 --amount 50 --timeout 10000 //localhost:3040
Running 50 requests test @ //localhost:3040
50 connections

50 requests in 122.73s, 7.7 kB read

Trong trường hợp này, chúng tôi đã tiết kiệm được thời gian chờ tổng thể—người dùng sẽ không phải đợi hơn 122 giây—nhưng chúng tôi đã mất thời gian phản hồi trung bình—mọi người hiện phải đợi 122 giây. Vì vậy, ngay cả đồng thời cũng không phải là viên đạn bạc—nó giúp chúng tôi xử lý nhiều yêu cầu cùng lúc nhưng mất nhiều thời gian hơn vì chúng tôi đang làm nhiều việc cùng một lúc

Tuy nhiên, 122 giây là một khoảng thời gian quá dài để người dùng có thể đợi phản hồi. Nhưng bạn không cần phải lo lắng về điều đó. Trong thế giới thực, API của bạn sẽ trả về phản hồi trong vòng một giây, do đó, thiết lập đồng thời có thể xử lý 50 yêu cầu trong vài giây

Vượt qua những hạn chế

Xử lý nhiều yêu cầu là một yêu cầu đối với bất kỳ ứng dụng web nào, nhưng hơn thế nữa nếu bạn đang chạy một ứng dụng có tính tương tác cao, chẳng hạn như máy chủ websockets. Việc buộc mỗi kết nối phải đợi các kết nối trước đó có thể sẽ dẫn đến việc ổ cắm hết thời gian chờ trước khi bạn truy cập được. Trong ngữ cảnh không phải web [chẳng hạn như trò chơi], các tương tác của người dùng sẽ bị treo nếu mọi tương tác đơn lẻ và hành động mà nó gây ra phải được xử lý trước khi có thể thực hiện bất kỳ điều gì khác

Vậy làm cách nào để vượt qua giới hạn đơn luồng này và đạt được tính đồng thời [tốt hơn]?

câu trả lời trực tiếp. thêm chủ đề. Chúng tôi có thể tăng số lượng chủ đề bằng cách thêm nhiều máy chủ hơn. Nếu tôi bắt đầu nhiều PHP hoặc Node. js có bộ cân bằng tải phân phối các yêu cầu giữa chúng, thì tôi có thể xử lý đồng thời nhiều yêu cầu hơn

Tạo ra các quy trình mới có thể là một hoạt động tốn kém. Các máy chủ chuyên dụng như Apache và Nginx thực hiện việc này hiệu quả hơn—chúng xử lý việc tạo và quản lý các quy trình hoặc luồng cho từng yêu cầu. Đây là một bài đọc hay về cách Nginx thực hiện

Nếu bạn vẫn muốn đạt được đồng thời trong một máy chủ PHP, có một số dự án cố gắng cung cấp điều này, đáng chú ý nhất trong số đó là ReactPHP và Amp. Laravel Octane được phát hành gần đây cũng cung cấp điều này, với API sạch hơn nhiều so với một dự án khác, Swoole

Đây là giao diện của một máy chủ web đồng thời với Amp

________số 8

Đúng, các yêu cầu chồng chéo. ✅

Đồng thời đi kèm với những thách thức riêng của nó. Nó làm phức tạp mọi thứ. Mã của bạn phải biết rằng nó sẽ được chạy đồng thời, nếu không bạn sẽ trở thành nạn nhân của rất nhiều lỗi. Và có cả một lớp các vấn đề tương tranh trong khoa học máy tính, mà mọi người đã nghiên cứu và giải quyết trong nhiều thập kỷ. Nhưng điều đó cực kỳ quan trọng trong việc cung cấp trải nghiệm tốt cho người dùng và tôi hy vọng sẽ hiểu rõ hơn về điều đó khi tôi tiếp tục

Tôi rất vui khi chạy các thử nghiệm này để xác minh một số giả định của mình về các nền tảng tôi sử dụng và tôi hy vọng bạn thích đọc bài này. Thử nghiệm là một cách tuyệt vời để học hỏi. Tôi khuyên bạn nên tự chạy thử nghiệm và thậm chí thiết kế thử nghiệm của riêng mình

Này👋. Tôi viết về những thách thức kỹ thuật phần mềm thú vị. Bạn muốn được cập nhật khi tôi xuất bản bài viết mới? . ứng dụng/blog. shalvah. tôi

[Lời thú tội. Tôi đã xây dựng xúc tu. ✋ Nó giúp bạn giữ hộp thư đến sạch sẽ bằng cách kết hợp các blog yêu thích của bạn vào một bản tin hàng tuần. ]

PHP có sử dụng nhiều luồng không?

Các ứng dụng PHP chắc chắn hoạt động hiệu quả với khả năng đa luồng . Đa luồng là một cái gì đó tương tự như đa nhiệm, nhưng nó cho phép xử lý nhiều công việc cùng một lúc, thay vì trên nhiều quy trình.

Tại sao PHP là luồng đơn?

Bản chất luồng đơn của PHP có nghĩa là PHP không có bất kỳ hỗ trợ tích hợp nào để tạo luồng mới trong quá trình thực thi tập lệnh . Tuy nhiên, điều này không có nghĩa là bạn không thể có hai lần thực thi đồng thời cùng một tập lệnh. Trong thiết lập phổ biến nhất, trang web của bạn được phục vụ bởi Apache HTTPD.

PHP 8 có đa luồng không?

Một ứng dụng sử dụng luồng nếu nó yêu cầu xử lý song song. Nói cách khác, bằng một chương trình, chúng ta có thể xử lý song song nhiều đơn vị lệnh. PHP không cung cấp chức năng đa luồng sẵn có , chúng ta cần thêm các “luồng” gói/tiện ích mở rộng vào PHP của mình.

Chủ đề PHP là gì?

Luồng là đơn vị lệnh nhỏ có thể được thực thi bởi bộ xử lý . Một ứng dụng sử dụng luồng nếu nó yêu cầu xử lý song song. Nói cách khác, bằng một chương trình duy nhất, chúng ta có thể xử lý song song nhiều đơn vị lệnh.

Chủ Đề