2010-10-20 15 views
6

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 đó:

  1. 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ì.

  2. 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.

  3. 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.

+0

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. –

+1

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ả. –

+0

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. –

Trả lời

0

Bạn chỉ cần sử dụng cơ chế đồng bộ hóa Chủ đề chuẩn của luồng Delphi.

Kiểm tra trợ giúp IDE của bạn cho lớp TEvent và các phương pháp liên quan của nó.

1

Tôi không biết bất kỳ lớp truy cập cơ sở dữ liệu nào cho phép điều này.

Lý do là mỗi chuỗi có "thread local storage" riêng của mình (Từ khóa threadvar ở Delphi, các ngôn ngữ khác có tương đương, nó được sử dụng trong nhiều khung công tác).
Khi bạn khởi động mọi thứ trên một luồng và tiếp tục trên một chuỗi khác, khi đó bạn sẽ nhận được các kho lưu trữ cục bộ này trộn lẫn gây ra tất cả các loại havoc.

Điều tốt nhất bạn có thể làm là thế này:

  1. vượt qua truy vấn và các thông số để các chủ đề mà sẽ xử lý này (sử dụng các cơ chế đồng bộ hóa thread chuẩn Delphi cho việc này)
  2. có thread truy vấn thực tế thực hiện truy vấn
  3. trả lại kết quả cho các chủ đề chính (sử dụng các cơ chế đồng bộ hóa thread chuẩn Delphi cho điều này)

Câu trả lời cho this question giải thích đồng bộ hóa chuỗi chi tiết hơn.

Edit: (trên chậm coi bắt đầu một cái gì đó trong một chủ đề khác)

"Ngay lập tức" là một thuật ngữ tương đối: nó phụ thuộc vào cách bạn làm đồng bộ hóa thread của bạn và có thể rất rất nhanh (ví dụ: ít hơn một phần nghìn giây).
Tạo chuỗi mới có thể mất chút thời gian.
Giải pháp là để có một luồng của các chuỗi công nhân đủ lớn để phục vụ một lượng yêu cầu hợp lý một cách hiệu quả.
Bằng cách đó, nếu hệ thống chưa quá bận, bạn sẽ có một chuỗi công nhân sẵn sàng để bắt đầu phục vụ yêu cầu của bạn gần như ngay lập tức.

Tôi đã thực hiện điều này (thậm chí là quá trình chéo) trong một ứng dụng âm thanh lớn yêu cầu đáp ứng độ trễ thấp và nó hoạt động như một nét duyên dáng.
Quy trình máy chủ âm thanh chạy ở mức độ ưu tiên cao đang chờ yêu cầu. Khi nó không hoạt động, nó không tiêu thụ CPU, nhưng khi nó nhận được một yêu cầu, nó phản hồi rất nhanh.

Câu trả lời cho this question on changes with big improvementsthis question on cross thread communication cung cấp một số mẹo thú vị về cách thực hiện hành vi không đồng bộ này.
Tìm kiếm các từ AsyncCalls, OmniThreadthread.

--jeroen

+0

Đó là cách tôi đã làm một vài phiên bản trước đây. Nó quá chậm. Trong mọi trường hợp, nó không giải quyết được vấn đề - đó là đảm bảo rằng luồng gửi truy vấn ngay lập tức. –

+0

@Guy: xem chỉnh sửa của tôi. –

+0

Cảm ơn bạn đã làm rõ. Tôi sẽ mở rộng giải thích của tôi về cách tôi làm điều đó ngay bây giờ bằng cách chỉnh sửa câu hỏi ở trên. (Những hộp bình luận này là nhỏ.) Bình luận và/hoặc phê bình được hoan nghênh. –

1

tôi đưa vào một câu trả lời thứ hai, về phần thứ hai của bạn câu hỏi: bạn Scheduling Vấn đề Điều này làm cho nó dễ dàng hơn để phân biệt cả hai câu trả lời.

Trước hết, bạn nên đọc Consequences of the scheduling algorithm: Sleeping doesn't always help là một phần của blog của Raymond Chen "The Old New Thing".
Sleeping versus polling cũng là đọc tốt.
Về cơ bản all these đọc tốt.

Nếu tôi hiểu vấn đề Scheduling của bạn một cách chính xác, bạn có 3 loại chủ đề:

  1. Main Thread: đảm bảo các Fetch Chủ đề luôn luôn có việc phải làm
  2. Fetch Chủ đề: (cơ sở dữ liệu ràng buộc) lấy dữ liệu cho Chủ đề Processing
  3. Processing Chủ đề: (CPU bound) xử lý dữ liệu lấy

cách duy nhất để giữ 3 chạy là phải có 2 lấy dữ liệu càng nhiều càng tốt.
Cách duy nhất để giữ 2 lần tìm nạp, là có 1 cung cấp cho họ đủ mục nhập để tìm nạp.

Bạn có thể sử dụng hàng đợi để giao tiếp dữ liệu giữa 1 và 2 và giữa 2 và 3.

vấn đề của bạn bây giờ là hai lần:

  • tìm sự cân bằng giữa số lượng bài trong thể loại 2 và 3
  • đảm bảo rằng 2 luôn có việc phải làm

tôi nghĩ rằng bạn đã giải quyết được trước đây.
Vị trí thứ hai đi xuống để đảm bảo hàng đợi giữa 1 và 2 không bao giờ trống.

Một vài thủ thuật:

  • Bạn có thể sử dụng Sleep (1) (xem bài viết blog) như là một cách đơn giản để "buộc" 2 để chạy
  • Không để treads thoát của họ thực hiện: sáng tạo và phá hủy đề là tốn kém
  • chọn đối tượng đồng bộ của bạn (thường được gọi là đối tượng IPC) một cách cẩn thận (Kudzunice article trên chúng)

--jeroen

+0

Cảm ơn Jeroen. Đây là những gì tôi đang tìm kiếm. Tôi sẽ mất một lúc để đọc qua tất cả các liên kết đó. Như bạn có thể nói, tôi không hài lòng với cách tôi đang làm nó bây giờ. Nhưng chương trình bắt đầu đơn luồng, và thêm các chuỗi công việc đơn giản như tôi đã cho nó một hiệu suất rất lớn mà không cần nhiều phức tạp. Nó có thể là thời gian cho một tổng số viết lại bằng cách sử dụng hàng đợi và chủ đề trong một quá trình duy nhất. Có song song hơn để được khai thác trong quá trình chính. Những gì tôi đang làm bây giờ sẽ không mở rộng thêm nhiều lõi. –

+0

Cảm ơn Jeroen. Đây là những gì tôi đang tìm kiếm. Tôi sẽ mất một lúc để đọc qua tất cả các liên kết đó. Như bạn có thể nói, tôi không hài lòng với cách tôi đang làm nó bây giờ. Nhưng chương trình bắt đầu đơn luồng, và thêm các chuỗi công việc đơn giản như tôi đã cho nó một hiệu suất rất lớn mà không cần nhiều phức tạp. Nó có thể là thời gian cho một tổng số viết lại bằng cách sử dụng hàng đợi và chủ đề trong một quá trình duy nhất. Có song song hơn để được khai thác trong quá trình chính. Những gì tôi đang làm bây giờ sẽ không mở rộng thêm nhiều lõi. –

+0

@Guy: Tôi rất vui vì tôi có thể giúp đỡ. Đáng tiếc là rất ít người đã dành thời gian để đọc câu hỏi tuyệt vời của bạn, vì nó nên đã được upvoted hơn. Có lẽ bạn nên thay đổi tiêu đề câu hỏi của mình để tập trung hơn vào phần "lên lịch" của nó. –

4

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.

Tôi gặp vấn đề về khái niệm ở đây. Trong tình huống của bạn, tôi sẽ tạo ra một giải pháp đa tiến trình, với mỗi quá trình làm mọi thứ trong chuỗi đơn của nó, hoặc tôi sẽ tạo ra một giải pháp đa luồng được giới hạn trong một cá thể đơn lẻ trên bất kỳ máy cụ thể nào. Một khi bạn quyết định làm việc với nhiều luồng và chấp nhận sự phức tạp và xác suất của các lỗi khó sửa, bạn nên tận dụng tối đa chúng. Sử dụng một tiến trình đơn lẻ với nhiều luồng cho phép bạn sử dụng các số luồng khác nhau để đọc và ghi vào cơ sở dữ liệu và xử lý dữ liệu của bạn. Số lượng các chủ đề thậm chí có thể thay đổi trong thời gian chạy chương trình của bạn, và tỷ lệ của cơ sở dữ liệu và xử lý chủ đề có thể quá. Loại phân vùng động này chỉ có thể thực hiện được nếu bạn có thể kiểm soát tất cả các luồng từ một điểm trong chương trình, điều này không thể thực hiện được với nhiều tiến trình.

Tôi đã triển khai đa luồng trong quá trình này chỉ để tránh chặn cuộc gọi SQL.

Với nhiều quy trình, sẽ không có nhu cầu thực sự. Nếu quá trình của bạn là I/O-ràng buộc một số thời gian họ không tiêu thụ tài nguyên CPU, vì vậy bạn có thể chỉ cần chạy nhiều hơn chúng so với máy tính của bạn có lõi. Nhưng sau đó bạn có vấn đề để biết có bao nhiêu quá trình để sinh sản, và điều đó có thể thay đổi một lần nữa theo thời gian nếu máy làm công việc khác quá. Một giải pháp luồng trong một quá trình có thể được thực hiện thích nghi với một môi trường thay đổi một cách tương đối đơn giản.

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.

Bạn nên rời khỏi hệ điều hành. Đơn giản chỉ cần có một quy trình duy nhất với các chủ đề được gộp chung cần thiết. Giống như sau:

  • Một số bài đọc hồ sơ từ cơ sở dữ liệu và thêm chúng vào một hàng đợi nhà sản xuất-tiêu dùng với một giới hạn trên, đó là nơi nào đó giữa N2 * N nơi N là số lõi bộ xử lý trong hệ thống. Những chủ đề này sẽ chặn trên hàng đợi đầy đủ và chúng có thể có mức độ ưu tiên tăng lên, do đó chúng sẽ được lên lịch để chạy ngay khi hàng đợi có nhiều khoảng trống hơn và chúng bị bỏ cấm. Vì chúng sẽ bị chặn trên I/O hầu hết thời gian, ưu tiên cao hơn của chúng không phải là vấn đề.
    Tôi không biết số lượng chủ đề đó là bao nhiêu, bạn cần phải đo lường.

  • Một số luồng xử lý, có thể là một luồng cho mỗi lõi bộ xử lý trong hệ thống. Họ sẽ lấy các mục công việc từ hàng đợi được đề cập ở điểm trước đó, trên khối trên hàng đợi đó nếu nó trống. Các mục công việc đã xử lý phải đi đến hàng đợi khác.

  • Một số chủ đề xử lý các mục công việc từ hàng đợi thứ hai và ghi dữ liệu trở lại cơ sở dữ liệu. Có lẽ phải có giới hạn trên cho hàng đợi thứ hai, để làm cho nó không thể ghi dữ liệu đã xử lý trở lại cơ sở dữ liệu sẽ không làm cho dữ liệu được xử lý chồng chất và lấp đầy toàn bộ không gian bộ nhớ của bạn.

Số lượng chủ đề cần được xác định, nhưng tất cả lịch biểu sẽ được thực hiện bởi trình lên lịch OS. Điều quan trọng là phải có đủ luồng để sử dụng tất cả các lõi CPU và số lượng các chuỗi phụ trợ cần thiết để giữ cho chúng bận rộn và xử lý các kết quả đầu ra của chúng. Nếu các chủ đề này đến từ các hồ bơi, bạn có thể tự do điều chỉnh số của chúng trong thời gian chạy.

Omni Thread Library có giải pháp cho các tác vụ, nhóm tác vụ, hàng đợi người tiêu dùng của nhà sản xuất và mọi thứ khác mà bạn cần thực hiện việc này. Nếu không, bạn có thể viết hàng đợi của riêng bạn bằng cách sử dụng mutexes.

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ằng cách ưu tiên cao hơn.

Hệ điều hành lên lịch không biết rằng điều quan trọng là cho nhiệm vụ prefetch phát hành các truy vấn ngay lập tức

Nó sẽ biết nếu thread có một ưu tiên cao hơn.

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.

Chỉ dành cho các chủ đề có cùng mức độ ưu tiên. Không có chuỗi ưu tiên thấp hơn nào sẽ nhận được bất kỳ phần nào của CPU trong khi luồng ưu tiên cao hơn trong cùng một tiến trình là runnable.
[Chỉnh sửa: Điều đó không hoàn toàn đúng, để biết thêm thông tin ở cuối. Tuy nhiên, nó là đủ gần với sự thật để đảm bảo rằng đề mạng ưu tiên cao hơn của bạn gửi và nhận dữ liệu càng sớm càng tốt.]

  1. Ngay sau khi ban hành nhiệm vụ prefetch, các chủ đề chính gọi Sleep (0).

Calling Sleep() là một cách xấu để buộc chủ đề để thực hiện theo một thứ tự nhất định. Đặt mức độ ưu tiên của luồng theo mức độ ưu tiên của công việc mà chúng thực hiện và sử dụng nguyên thủy của hệ điều hành để chặn luồng ưu tiên cao hơn nếu chúng không chạy.

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.

Không có gì bất thường về điều này. Đó là cách dự định sử dụng các luồng. Bạn chỉ phải đảm bảo rằng các chuỗi ưu tiên cao hơn chặn sớm hay muộn, và bất kỳ chuỗi nào đi tới hệ điều hành cho I/O (tệp hoặc mạng) sẽ chặn. Trong sơ đồ tôi phác thảo ở trên các chuỗi ưu tiên cao cũng sẽ chặn trên hàng đợi.

Tôi có thể phát hành truy vấn một cách không đồng bộ.

Tôi sẽ không đến đó. Kỹ thuật này có thể cần thiết khi bạn viết một máy chủ cho nhiều kết nối đồng thời và một chuỗi cho mỗi kết nối là cực kỳ tốn kém, nhưng nếu không chặn truy cập mạng trong một giải pháp luồng sẽ hoạt động tốt.

Edit:

Nhờ Jeroen Pluimers cho poke để xem xét kỹ hơn vào này. Vì thông tin trong các liên kết mà anh đưa ra trong nhận xét của anh cho thấy tuyên bố của tôi

Không có chuỗi ưu tiên thấp hơn sẽ nhận được bất kỳ lát CPU nào trong khi chuỗi ưu tiên cao hơn trong cùng một quy trình được chạy.

không đúng. Chủ đề ưu tiên thấp hơn mà không chạy trong một thời gian dài có được mức tăng ưu tiên ngẫu nhiên và thực sự sớm hay muộn sẽ có được một phần của CPU, mặc dù các luồng ưu tiên cao hơn là runnable. Để biết thêm thông tin về việc này, xem cụ thể "Priority Inversion and Windows NT Scheduler".

Để kiểm tra này ra tôi đã tạo ra một bản demo đơn giản với Delphi:

type 
    TForm1 = class(TForm) 
    Label1: TLabel; 
    Label2: TLabel; 
    Label3: TLabel; 
    Label4: TLabel; 
    Label5: TLabel; 
    Label6: TLabel; 
    Timer1: TTimer; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Timer1Timer(Sender: TObject); 
    private 
    fLoopCounters: array[0..5] of LongWord; 
    fThreads: array[0..5] of TThread; 
    end; 

var 
    Form1: TForm1; 

implementation 

{$R *.DFM} 

// TTestThread 

type 
    TTestThread = class(TThread) 
    private 
    fLoopCounterPtr: PLongWord; 
    protected 
    procedure Execute; override; 
    public 
    constructor Create(ALowerPriority: boolean; ALoopCounterPtr: PLongWord); 
    end; 

constructor TTestThread.Create(ALowerPriority: boolean; 
    ALoopCounterPtr: PLongWord); 
begin 
    inherited Create(True); 
    if ALowerPriority then 
    Priority := tpLower; 
    fLoopCounterPtr := ALoopCounterPtr; 
    Resume; 
end; 

procedure TTestThread.Execute; 
begin 
    while not Terminated do 
    InterlockedIncrement(PInteger(fLoopCounterPtr)^); 
end; 

// TForm1 

procedure TForm1.FormCreate(Sender: TObject); 
var 
    i: integer; 
begin 
    for i := Low(fThreads) to High(fThreads) do 
// fThreads[i] := TTestThread.Create(True, @fLoopCounters[i]); 
    fThreads[i] := TTestThread.Create(i >= 4, @fLoopCounters[i]); 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
var 
    i: integer; 
begin 
    for i := Low(fThreads) to High(fThreads) do begin 
    if fThreads[i] <> nil then 
     fThreads[i].Terminate; 
    end; 
    for i := Low(fThreads) to High(fThreads) do 
    fThreads[i].Free; 
end; 

procedure TForm1.Timer1Timer(Sender: TObject); 
begin 
    Label1.Caption := IntToStr(fLoopCounters[0]); 
    Label2.Caption := IntToStr(fLoopCounters[1]); 
    Label3.Caption := IntToStr(fLoopCounters[2]); 
    Label4.Caption := IntToStr(fLoopCounters[3]); 
    Label5.Caption := IntToStr(fLoopCounters[4]); 
    Label6.Caption := IntToStr(fLoopCounters[5]); 
end; 

Điều này tạo ra 6 đề (trên 4 máy lõi của tôi), hoặc là tất cả với ưu tiên thấp hơn, hoặc 4 với bình thường và 2 với ưu tiên thấp hơn . Trong trường hợp đầu tiên tất cả 6 chủ đề chạy, nhưng với cổ phiếu trái ngược nhau về thời gian CPU:

6 threads with lower priority

Trong trường hợp thứ hai 4 luồng chạy với cổ phiếu tương đương thời gian CPU, nhưng hai chủ đề khác có được một chút cổ phiếu của CPU cũng như:

4 threads with normal, 2 threads with lower priority

Nhưng tỷ lệ thời gian CPU là rất rất nhỏ, cách dưới một phần trăm của những gì các chủ đề khác nhận được.

Và để quay lại câu hỏi của bạn: Một chương trình sử dụng nhiều chủ đề có mức độ ưu tiên tùy chỉnh, được kết hợp với hàng đợi của nhà sản xuất, nên là giải pháp khả thi. Trong trường hợp bình thường, các luồng cơ sở dữ liệu sẽ chặn hầu hết thời gian, hoặc trên các hoạt động mạng hoặc trên các hàng đợi. Và bộ lập lịch Windows sẽ đảm bảo rằng ngay cả một chuỗi ưu tiên thấp hơn cũng sẽ không hoàn toàn chết đói.

+0

'Không có chuỗi ưu tiên thấp hơn sẽ nhận được bất kỳ lát CPU nào trong khi chuỗi ưu tiên cao hơn trong cùng một tiến trình là runnable.'; Tôi không chắc đó là sự thật, hãy xem: http://blogs.msdn.com/b/oldnewthing/archive/2005/10/03/476413.aspx +1 cho phần còn lại của câu trả lời đúng của bạn. –

+0

@Jeroen: Một chuỗi đang chạy có nghĩa là nó hiện không chạy. Vì vậy, một lõi miễn phí sẽ chỉ chạy chuỗi ưu tiên thấp hơn nếu tất cả các chuỗi ưu tiên cao hơn đang chạy hoặc bị chặn. Tôi có nên chỉnh sửa câu trả lời của mình cho phù hợp không? – mghie

+0

@mghie: Vui lòng thực hiện; xin vui lòng cũng nghiên cứu nếu đó thực sự là những gì sẽ xảy ra. Tôi dường như nhớ lại rằng các luồng có mức độ ưu tiên thấp có thể nhận được một số thời gian CPU, mặc dù có các luồng có mức ưu tiên cao đang chạy. Ngoài ra còn có một cái gì đó như "ưu tiên động" và "phần quan trọng". Trình lên lịch Windows Thread là một con thú phức tạp; nó làm cho rất nhiều đọc thú vị, ví dụ: http://stackoverflow.com/questions/656959/win32-thread-scheduling, http://support.microsoft.com/kb/96418 và http: // msdn. microsoft.com/en-us/library/ms684831(VS.85).aspx –

Các vấn đề liên quan