Tôi có một chương trình thực hiện một dạng đa luồng giới hạn. Nó được viết bằng Delphi và sử dụng libmysql.dll (API C) để truy cập vào máy chủ MySQL. Chương trình phải xử lý một danh sách dài các bản ghi, lấy 0,1 giây cho mỗi bản ghi. Hãy nghĩ về nó như một vòng lặp lớn. Tất cả các truy cập cơ sở dữ liệu được thực hiện bởi các luồng công nhân, hoặc tìm nạp trước các bản ghi tiếp theo hoặc ghi kết quả, do đó, luồng chính không phải đợi.Làm cách nào để tạo chuỗi truy vấn SQL, sau đó thực hiện công việc khác trước khi nhận kết quả?
Ở đầu vòng lặp này, trước tiên chúng tôi chờ chuỗi tìm nạp trước, nhận kết quả, sau đó có chuỗi tìm nạp trước thực hiện truy vấn cho bản ghi tiếp theo. Ý tưởng cho rằng chuỗi tìm nạp trước sẽ gửi truy vấn ngay lập tức và chờ kết quả trong khi luồng chính hoàn thành vòng lặp.
Nó thường hoạt động theo cách đó. Nhưng lưu ý không có gì để đảm bảo rằng chuỗi tìm nạp trước chạy ngay lập tức. Tôi thấy rằng truy vấn thường không được gửi cho đến khi chuỗi chính lặp lại và bắt đầu chờ tìm nạp trước.
Tôi sắp xếp cố định bằng cách gọi ngủ (0) ngay sau khi khởi chạy chuỗi tìm nạp trước. Bằng cách này, chủ đề chính từ bỏ phần còn lại của lát thời gian của nó, hy vọng rằng chuỗi tìm nạp trước sẽ chạy, gửi truy vấn. Sau đó, rằng luồng sẽ ngủ trong khi chờ, cho phép chủ đề chính chạy lại.
Tất nhiên, có nhiều chủ đề hơn đang chạy trong hệ điều hành, nhưng điều này đã thực sự làm việc ở một mức độ nào đó.
Điều tôi thực sự muốn xảy ra là cho chuỗi chính gửi truy vấn và sau đó đợi luồng công nhân chờ kết quả. Sử dụng libmysql.dll Tôi gọi số
result := mysql_query(p.SqlCon,pChar(p.query));
trong chuỗi công nhân. Thay vào đó, tôi muốn có chủ đề chính gọi là
mysql_threadedquery(p.SqlCon,pChar(p.query),thread);
sẽ tắt nhiệm vụ ngay sau khi dữ liệu xuất hiện.
Bất kỳ ai biết về bất kỳ điều gì giống như vậy?
Đây thực sự là một vấn đề lập kế hoạch, vì vậy tôi có thể thử đang xoá chuỗi tìm nạp trước ở mức độ ưu tiên cao hơn, sau đó giảm mức ưu tiên của nó sau khi truy vấn được gửi. Nhưng một lần nữa, tôi không có bất kỳ cuộc gọi mysql nào tách truy vấn khỏi nhận kết quả.
Có thể nó ở trong đó và tôi không biết về nó. Hãy soi sáng cho tôi.
Đã thêm câu hỏi:
Có ai nghĩ rằng vấn đề này sẽ được giải quyết bằng cách chạy chuỗi tìm nạp có mức độ ưu tiên cao hơn chủ đề chính không? Ý tưởng là prefetch sẽ ngay lập tức preempt thread chính và gửi truy vấn. Sau đó, nó sẽ ngủ chờ đợi cho máy chủ trả lời. Trong khi đó, luồng chính sẽ chạy.
Đã thêm: Chi tiết triển khai hiện tại
Chương trình này thực hiện tính toán trên dữ liệu chứa trong một DB MySQL. Có 33M mục được thêm vào mỗi giây. Chương trình chạy liên tục, xử lý các mục mới và đôi khi phân tích lại các mục cũ. Nó nhận được một danh sách các mục để phân tích từ một bảng, do đó, ở đầu của một vượt qua (mục hiện tại) nó biết ID mục tiếp theo nó sẽ cần.
Vì mỗi mục độc lập, đây là mục tiêu hoàn hảo để xử lý đa.Cách dễ nhất để làm điều này là chạy nhiều phiên bản của chương trình trên nhiều máy. Chương trình được tối ưu hóa cao thông qua việc thiết kế lại hồ sơ, viết lại và thuật toán. Tuy nhiên, một cá thể đơn lẻ sử dụng 100% lõi CPU khi không bị đói dữ liệu. Tôi chạy 4-8 bản trên hai máy trạm quad-core. Nhưng với tốc độ này, họ phải dành thời gian chờ đợi trên máy chủ MySQL. (Tối ưu hóa lược đồ Máy chủ/DB là một chủ đề khác.)
Tôi đã triển khai đa luồng trong quá trình này để tránh bị chặn trong các cuộc gọi SQL. Đó là lý do tại sao tôi gọi đây là "giới hạn đa luồng". Một chuỗi công nhân có một nhiệm vụ: gửi một lệnh và chờ kết quả. (OK, hai nhiệm vụ.)
Hóa ra có 6 tác vụ chặn được liên kết với 6 bảng. Hai trong số các dữ liệu đọc và 4 kết quả ghi khác. Đây là tương tự, đủ để được xác định bởi một cấu trúc nhiệm vụ chung. Một con trỏ tới nhiệm vụ này được chuyển đến một trình quản lý threadpool, gán một luồng để thực hiện công việc. Chủ đề chính có thể kiểm tra trạng thái tác vụ thông qua cấu trúc Nhiệm vụ.
Điều này làm cho mã chuỗi chính rất đơn giản. Khi cần thực hiện Task1, nó đợi Task1 không bận, đặt lệnh SQL trong Task1 và tắt nó đi. Khi Task1 không còn bận, nó có chứa kết quả (nếu có).
4 tác vụ viết kết quả là không đáng kể. Chủ đề chính có một bản ghi tác vụ ghi trong khi nó đi vào mục tiếp theo. Khi hoàn thành với mục đó, nó đảm bảo việc ghi trước đã hoàn thành trước khi bắt đầu một mục khác.
2 chuỗi đọc ít tầm thường hơn. Không có gì có thể đạt được bằng cách truyền đọc cho một chuỗi và sau đó chờ kết quả. Thay vào đó, các tác vụ này tìm nạp trước dữ liệu cho mục tiếp theo. Vì vậy, các chủ đề chính, đến nhiệm vụ này chặn, kiểm tra nếu prefetch được thực hiện; Chờ đợi nếu cần thiết cho việc tìm nạp trước để kết thúc, sau đó lấy dữ liệu từ Nhiệm vụ. Cuối cùng, nó tái phát hành Nhiệm vụ với ID mục NEXT.
Ý tưởng dành cho tác vụ tìm nạp trước để ngay lập tức phát hành truy vấn và chờ máy chủ MySQL. Sau đó, thread chính có thể xử lý Item hiện tại và theo thời gian nó bắt đầu trên Item tiếp theo, dữ liệu cần thiết trong nhiệm vụ prefetch.
Vì vậy, luồng, nhóm luồng, đồng bộ hóa, cấu trúc dữ liệu, v.v ... đều được thực hiện. Và tất cả đều hoạt động. Những gì tôi còn lại là một vấn đề lập kế hoạch.
Vấn đề lập lịch biểu là: Tất cả tốc độ đạt được đang xử lý mục hiện tại trong khi máy chủ đang tìm nạp mục tiếp theo. Chúng tôi phát hành tác vụ tìm nạp trước khi xử lý mục hiện tại, nhưng làm cách nào để đảm bảo rằng nó bắt đầu? Bộ lập lịch hệ điều hành không biết rằng điều quan trọng đối với nhiệm vụ tìm nạp trước là đưa ra truy vấn ngay lập tức, và sau đó nó sẽ không làm gì ngoài chờ đợi.
Trình lên lịch hệ điều hành đang cố gắng "công bằng" và cho phép mỗi tác vụ chạy trong một lát thời gian được chỉ định. Trường hợp xấu nhất của tôi là: Chủ đề chính nhận được slice của nó và phát hành một tìm nạp trước, sau đó kết thúc mục hiện tại và phải đợi cho mục tiếp theo. Chờ đợi phát hành phần còn lại của lát thời gian của nó, do đó, trình lập lịch bắt đầu chuỗi tìm nạp trước, phát hành truy vấn và chờ đợi. Bây giờ cả hai chủ đề đang chờ đợi. Khi máy chủ báo hiệu truy vấn được thực hiện, chuỗi tìm nạp lại khởi động lại và yêu cầu kết quả (tập dữ liệu) sau đó ngủ. Khi máy chủ cung cấp kết quả, chuỗi tìm kiếm prefetch sẽ đánh dấu, đánh dấu Task Done và kết thúc. Cuối cùng, thread chính khởi động lại và lấy dữ liệu từ Task đã hoàn thành.
Để tránh lịch biểu trường hợp xấu nhất này, tôi cần một số cách để đảm bảo rằng truy vấn tìm nạp trước được đưa ra trước khi chuỗi chính tiếp tục với mục hiện tại. Cho đến nay tôi đã nghĩ đến ba cách để làm điều đó:
Ngay sau khi phát hành nhiệm vụ tìm nạp trước, chủ đề chính gọi là Sleep (0).Điều này sẽ từ bỏ phần còn lại của lát thời gian của nó. Sau đó tôi hy vọng rằng trình lập lịch biểu chạy chuỗi tìm nạp trước, chuỗi này sẽ phát hành truy vấn và sau đó đợi. Sau đó, lịch trình nên khởi động lại chủ đề chính (tôi hy vọng.) Như xấu như nó âm thanh, điều này thực sự hoạt động tốt hơn không có gì.
Tôi có thể phát hành chuỗi tìm nạp có mức độ ưu tiên cao hơn chủ đề chính. Điều đó sẽ khiến cho trình lập lịch biểu chạy nó ngay lập tức, ngay cả khi nó phải chặn luồng chính. Nó cũng có thể có tác dụng không mong muốn. Có vẻ như không tự nhiên đối với chuỗi công nhân nền để có mức độ ưu tiên cao hơn.
Tôi có thể phát hành truy vấn không đồng bộ. Tức là, tách riêng truy vấn để gửi kết quả. Bằng cách đó tôi có thể có chủ đề chính gửi tìm nạp trước bằng cách sử dụng mysql_send_query (không chặn) và tiếp tục với mục hiện tại. Sau đó, khi cần mục tiếp theo, nó sẽ gọi mysql_read_query, nó sẽ chặn cho đến khi dữ liệu có sẵn.
Lưu ý rằng giải pháp 3 thậm chí không sử dụng chuỗi công nhân. Điều này trông giống như câu trả lời hay nhất, nhưng yêu cầu viết lại một số mã cấp thấp. Tôi hiện đang tìm kiếm các ví dụ về truy cập máy chủ-máy chủ không đồng bộ như vậy.
Tôi cũng muốn có bất kỳ ý kiến có kinh nghiệm về các phương pháp này. Tôi đã bỏ lỡ bất cứ điều gì, hoặc tôi làm bất cứ điều gì sai? Xin lưu ý rằng đây là tất cả mã làm việc. Tôi không hỏi làm thế nào để làm điều đó, nhưng làm thế nào để làm điều đó tốt hơn/nhanh hơn.
Nhìn vào wrapper mysql.pas Tôi tìm thấy hai hàm mysql_send_query và mysql_read_query nghe giống như những gì tôi cần. Google sau đó đã cho tôi để http://jan.kneschke.de/2008/9/9/async-mysql-queries-with-c-api/ người viết: "... là công khai, nhưng không có giấy tờ. Vâng, nhưng đó không ngăn chúng ta. " Điều này có vẻ đầy hứa hẹn, nhưng tôi vẫn có thể sử dụng lời khuyên về cách làm đúng. –
Thông thường bạn có một chuỗi xử lý một truy vấn, không phải là một truy vấn gửi một truy vấn và một truy vấn khác chờ kết quả. –
Vâng, tôi làm điều đó ngay bây giờ. Tôi có một chuỗi prefetch gửi truy vấn để chọn bản ghi tiếp theo. Về lý thuyết, tôi nên xử lý bản ghi hiện tại trong khi luồng chờ kết quả. Trong thực tế, không có gì đảm bảo rằng chuỗi tìm nạp trước bắt đầu ngay lập tức. –