2012-01-26 25 views
7

Trước hết, tôi vẫn tự làm quen với đa luồng, và không biết nhiều thuật ngữ. Tôi cần phải chắc chắn rằng tôi đang làm điều này đúng, bởi vì nó là một chủ đề nhạy cảm.Quản lý số chủ đề động

Thông số kỹ thuật

Những gì tôi đang xây dựng là một thành phần trong đó sẽ chứa một số năng động của chủ đề. Mỗi chủ đề này được sử dụng lại để thực hiện một số yêu cầu. Tôi có thể cung cấp tất cả các chi tiết cần thiết cho chủ đề khi tạo và trước khi thực thi nó, cũng như cung cấp trình xử lý sự kiện. Một khi nó được thực hiện, tôi thực hiện khá nhiều với một yêu cầu, và tôi ăn trong một yêu cầu khác. Các yêu cầu đang được đưa vào các chủ đề này từ một chuỗi nền độc lập khác liên tục xử lý một hàng đợi các yêu cầu. Vì vậy, hệ thống này có hai danh sách: 1) Danh sách các hồ sơ yêu cầu, và 2) Danh sách các con trỏ thread.

Tôi đang sử dụng con cháu của lớp TThread (ít nhất đây là phương pháp luồng mà tôi quen thuộc). Tôi nhận được phản hồi từ các chủ đề bằng cách đồng bộ hóa trình kích hoạt sự kiện mà tôi đã chỉ định khi tạo chuỗi. Các chủ đề đang tải và lưu dữ liệu ở chế độ nền và khi chúng hoàn tất, chúng sẽ tự thiết lập lại để xử lý yêu cầu tiếp theo.

Vấn đề

Bây giờ những rắc rối bắt đầu khi quyết định làm thế nào để xử lý các trường hợp thay đổi số lượng các đề cho phép (thông qua một tài sản của các thành phần ActiveThreads: TActiveThreadRangeTActiveThreadRange = 1..20). Do đó, có thể có bất cứ nơi nào giữa 1 và 20 chủ đề được tạo ra tại một thời điểm. Nhưng khi, giả sử, ứng dụng sử dụng thành phần này sẽ thay đổi thuộc tính này từ 5 thành 3. Lúc này, đã có 5 luồng được tạo và tôi không muốn buộc luồng đó miễn phí nếu nó bận. Tôi cần phải chờ cho đến khi nó được thực hiện trước khi tôi giải phóng nó. Và mặt khác, nếu thuộc tính được thay đổi từ 3 đến 5, thì tôi cần phải tạo 2 luồng mới. Tôi cần phải biết cách tiếp cận thích hợp để 'theo dõi' các chủ đề này trong kịch bản này.

khả năng

Dưới đây là một số cách có thể tôi có thể nghĩ ra để 'theo dõi' những chủ đề ...

  • Giữ một TList chứa mỗi thread tạo - dễ dàng quản lý
  • Tạo một bao bọc TList hoặc hậu duệ chứa mỗi chuỗi được tạo - dễ quản lý hơn, nhưng nhiều công việc hơn
  • Giữ array contai ning mỗi thread được tạo ra - Điều này có tốt hơn một TList không?
  • Tạo một wrapper mảng chứa mỗi thread tạo

Nhưng sau đó trở lại vấn đề ban đầu của tôi - Phải làm gì với chủ đề bận rộn hiện khi tài sản ActiveThreads được giảm? Tạo chúng không có vấn đề gì, nhưng việc giải phóng chúng đang trở nên khó hiểu. Tôi thường tạo ra các chủ đề tự giải phóng, nhưng đây là lần đầu tiên tôi tạo ra một cái được tái sử dụng. Tôi chỉ cần biết phương pháp thích hợp để phá hủy những chủ đề này mà không làm gián đoạn nhiệm vụ của họ.

Cập nhật

Dựa trên những phản hồi, tôi đã mua và bắt đầu thực hiện OmniThreadLibrary (cũng như FastMM dài cần thiết). Tôi cũng đã thay đổi cách tiếp cận của tôi một chút - Một cách mà tôi có thể tạo ra các quy trình luồng mà không quản lý họ và không có một thread để xử lý hàng đợi ...

  • 1 phương pháp tổng thể để đẻ trứng một quá trình mới
    • function NewProcess(const Request: TProcessRequest): TProcessInfo;
    • TProcessRequest là một kỷ lục với thông số kỹ thuật của những gì cần phải làm (Tên file, tùy chọn, vv)
    • TProcessInfo là một kỷ lục mà họ sẽ trả lại một số thông tin trạng thái.
  • Cấp dữ liệu trong trình xử lý sự kiện cho trường hợp được 'thực hiện' với nhiệm vụ khi tạo quy trình mới. Khi thành phần nhận được thông báo này, nó sẽ kiểm tra hàng đợi.
    • Nếu lệnh được xếp hàng đợi, nó sẽ so sánh quá trình giới hạn hoạt động với quá trình hiện đếm
    • > Nếu vượt quá giới hạn, chỉ cần dừng lại và quá trình tiếp theo hoàn thành sẽ làm thực hiện cùng một séc
    • > Nếu trong giới hạn, Kick off khác quy trình mới (sau khi đảm bảo quá trình trước đó được thực hiện)
    • Nếu không có lệnh đang xếp hàng đợi, sau đó chỉ dừng lại
  • Mỗi quá trình có thể chết ngày của riêng mình sau khi đã thực hiện nhiệm vụ của mình (không có luồng giữ-sống)
  • tôi sẽ không phải lo lắng về timer khác hoặc sợi liên tục lặp qua
    • Thay vì mỗi quá trình phá hủy tự và kiểm tra nó cho các yêu cầu mới trước khi làm như vậy

Cập nhật Một

Tôi đã thực sự hoàn nguyên về sử dụng TThread vì OTL rất khó sử dụng. Tôi thích giữ những thứ được bọc và tổ chức trong lớp riêng của nó.

+0

Điều bạn đang hỏi về được gọi là "nhóm chủ đề"; có lẽ điều đó sẽ giúp bạn tìm tài nguyên. –

+5

Xem [delphi-threaded-list-of-thread-jobs-queueing] (http://stackoverflow.com/questions/1805633/delphi-threaded-list-of-thread-jobs-queueing). Và đừng cuộn hồ bơi chủ đề của riêng bạn, hãy xem [OTL-OmniThreadLibrary] (http://code.google.com/p/omnithreadlibrary/). –

+0

Trải nghiệm cá nhân của tôi là dễ dàng hơn khi làm việc với Windows API trực tiếp thay vì dựa vào 'TThread' nếu bạn đang thực hiện một số công việc phức tạp hơn. Thực tế, rất dễ dàng để bắt đầu luồng bằng cách sử dụng API Windows. Bắt đầu bằng cách thực hiện các thí nghiệm đơn giản với ['CreateThread'] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453 (v = vs.85) .aspx). Chỉ cần cẩn thận rằng bạn, với tư cách là một nhà phát triển Delphi, có lẽ nên sử dụng trình bao bọc 'System.BeginThread' thay cho' CreateThread', nhưng, tất nhiên, tài liệu MSDN 'CreateThread' vẫn hợp lệ. –

Trả lời

3

Về vấn đề của bạn với việc giảm các chủ đề đang hoạt động: xin lỗi, nhưng bạn chỉ cần tự quyết định. Hoặc là miễn phí các chủ đề không mong muốn ngay lập tức (mà chấm dứt chúng tại thời điểm sớm nhất có thể), hoặc để cho chúng chạy cho đến khi chúng được hoàn thành (kết thúc chúng sau khi tất cả công việc được thực hiện). Đó là sự lựa chọn của bạn. Tất nhiên bạn phải tách biến cho số mong muốn từ số lượng chủ đề thực tế. Vấn đề cho việc cập nhật số biến chủ đề thực tế (có thể chỉ đơn giản là một List.Count) là cho cả hai chính xác như nhau vì một trong hai giải pháp sẽ yêu cầu một thời gian.

Và về quản lý nhiều chuỗi: bạn có thể nghiên cứu this answer lưu trữ các chuỗi trong TList. Nó cần một chút chỉnh cho bạn danh sách mong muốn cụ thể mặc dù, xin vui lòng hét lên trong trường hợp cần hỗ trợ với điều đó. Ngoài ra, tất nhiên có nhiều triển khai có thể được bắt nguồn từ việc sử dụng TThread mặc định. Và lưu ý rằng có tồn tại các thư viện luồng (đa) khác, nhưng tôi chưa bao giờ có nhu cầu sử dụng chúng.

+1

Không có sự khác biệt giữa giải phóng ngay lập tức và chờ cho đến khi hoàn thành. 'Destroy' gọi' WaitFor'. –

+2

FreeOnTerminate: = true. Vượt qua một nhiệm vụ thuốc độc trên hàng đợi nhiệm vụ. Không có cờ, không có TThread.WaitFor, không cần truy cập cá thể TThread, vì vậy không có danh sách các chủ đề cần thiết, do đó không cần quản lý danh sách luồng, vì vậy hầu hết mã tạo bế tắc tinh quái bị loại bỏ. –

+0

@David Điều đó phụ thuộc vào nhiệm vụ của Chủ đề. Nếu chuỗi có vòng lặp kiểm tra độc đáo cho cờ Chấm dứt, thì có. Nếu thread chỉ có một điều và thiết lập Terminated to False không ảnh hưởng đến thời gian chạy, thì bạn đúng, nhưng sau đó tôi cũng không hiểu câu hỏi của OP. – NGLN

5

Như đã giải thích bởi @NGLN, bạn cần phải nhóm một số chủ đề và chấp nhận cách dễ nhất để quản lý số chuỗi là ly dị số lượng chủ đề thực tế từ số mong muốn. Việc thêm chủ đề vào nhóm là dễ dàng - chỉ cần tạo thêm một số trường hợp, (chuyển hàng đợi nhập nhiệm vụ của người sản xuất-người tiêu dùng làm tham số để chủ đề biết phải đợi gì). Nếu số lượng chủ đề mong muốn ít hơn số lượng hiện tại hiện tại, bạn có thể xếp hàng đủ 'thuốc độc' để tiêu diệt các chuỗi phụ.

Không giữ bất kỳ danh sách chỉ dẫn chuỗi nào - đó là tải trọng của các rắc rối quản lý vi mô mà không cần thiết, (và có thể sẽ xảy ra sai). Tất cả những gì bạn cần giữ là đếm số lượng chuỗi mong muốn trong nhóm để bạn biết hành động cần thực hiện khi có điều gì đó thay đổi thuộc tính 'poolDepth'.

Trình kích hoạt sự kiện được nạp tốt nhất vào các công việc được phát hành cho nhóm - chuyển tất cả chúng từ một số lớp 'TpooledTask' có sự kiện làm tham số hàm dựng và lưu trữ nó trong một số 'FonComplete' TNotifyEvent. Các thread chạy nhiệm vụ có thể gọi FonComplete khi nó được thực hiện công việc, (với TpooledTask như tham số người gửi) - bạn không cần phải biết những gì thread chạy nhiệm vụ.

Ví dụ:

unit ThreadPool; 

    interface 

    uses 
     Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, 
     Dialogs, StdCtrls, contnrs, syncobjs; 


    type 

    TpooledTask=class(TObject) 
    private 
     FonComplete:TNotifyEvent; 
    protected 
     Fparam:TObject; 
     procedure execute; virtual; abstract; 
    public 
     constructor create(onComplete:TNotifyEvent;param:TObject); 
    end; 

    TThreadPool=class(TObjectQueue) 
    private 
     access:TcriticalSection; 
     taskCounter:THandle; 
     threadCount:integer; 
    public 
     constructor create(initThreads:integer); 
     procedure addTask(aTask:TpooledTask); 
    end; 

    TpoolThread=class(Tthread) 
    private 
     FmyPool:TThreadPool; 
    protected 
     procedure Execute; override; 
    public 
     constructor create(pool:TThreadPool); 
    end; 

    implementation 

    { TpooledTask } 

    constructor TpooledTask.create(onComplete: TNotifyEvent; param: TObject); 
    begin 
     FonComplete:=onComplete; 
     Fparam:=param; 
    end; 

    { TThreadPool } 

    procedure TThreadPool.addTask(aTask: TpooledTask); 
    begin 
     access.acquire; 
     try 
     push(aTask); 
     finally 
     access.release; 
     end; 
     releaseSemaphore(taskCounter,1,nil); // release one unit to semaphore 
    end; 

    constructor TThreadPool.create(initThreads: integer); 
    begin 
     inherited create; 
     access:=TcriticalSection.create; 
     taskCounter:=createSemaphore(nil,0,maxInt,''); 
     while(threadCount<initThreads) do 
     begin 
     TpoolThread.create(self); 
     inc(threadCount); 
     end; 
    end; 

    { TpoolThread } 

    constructor TpoolThread.create(pool: TThreadPool); 
    begin 
     inherited create(true); 
     FmyPool:=pool; 
     FreeOnTerminate:=true; 
     resume; 
    end; 

procedure TpoolThread.execute; 
var thisTask:TpooledTask; 
begin 
    while (WAIT_OBJECT_0=waitForSingleObject(FmyPool.taskCounter,INFINITE)) do 
    begin 
    FmyPool.access.acquire; 
    try 
     thisTask:=TpooledTask(FmyPool.pop); 
    finally 
     FmyPool.access.release; 
    end; 
    thisTask.execute; 
    if assigned(thisTask.FonComplete) then thisTask.FonComplete(thisTask); 
    end; 
end; 

end. 
+1

@Jerry - để trả lời câu hỏi tiếp theo của bạn 'Làm thế nào tôi có thể gọi TThread.Sychronize nếu tôi không có một cá thể chủ đề?' - đừng làm thế. Nếu bạn cần kết quả chủ đề chính, hãy PostMessage nhiệm vụ trong trình xử lý OnComplete. –

+0

Phần lớn trong số này là tôi phải sử dụng lại các chủ đề này. Mỗi luồng sẽ được tạo và chạy không ngừng. Nếu nó không có yêu cầu để xử lý, nó sẽ chỉ chết. Nhưng khi nó được đưa ra một nhiệm vụ để thực hiện, nó sẽ tiếp tục được tái sử dụng cho mọi nhiệm vụ được đưa ra cho nó. Như đã đề cập ở trên, phương pháp 'quên' của bạn về chúng là những gì tôi đã quen - nhưng khái niệm tái sử dụng chúng là nơi tôi bị lạc. –

+0

Jerry, tôi thường có một danh sách an toàn chủ đề (hoặc một hàng đợi nếu bạn thích). Mỗi luồng có một liên kết đến hàng đợi này và ngủ khi nó trống. Miễn là có các mục để loại bỏ khỏi hàng đợi, các chủ đề đang bận làm việc. –

4

Bạn có thể thực hiện FreeNotify nhắn trong hàng đợi yêu cầu của bạn và khi Worker Chủ đề receieve thông điệp này tự giải phóng mình. Trong ví dụ của bạn khi bạn giảm số lượng chủ đề từ 5 xuống 3 chỉ cần đặt 2 thông báo FreeNotify trong hàng đợi của bạn và 2 chuỗi công việc sẽ miễn phí.

+1

Có - cách đơn giản nhất, không chấm dứt/WaitFor/OnTerminate, (tức là không có bế tắc nào) –

+0

+1 Âm thanh rất có thể là một ý tưởng hợp lý.Tất cả tôi sẽ phải tìm ra cách quyết định * cái nào * 2 trong số 5 là Nói cách khác, nếu tôi có 5 chủ đề, 3 trong số đó đang bận và 2 là nhàn rỗi, sau đó tôi sẽ muốn tiêu diệt những 2 đang nhàn rỗi. Không cần giúp đỡ với điều đó, chỉ để trỏ nó ra ... –

+1

Bạn không cần phải tìm ra ything Nếu bạn có 5 chủ đề, 3 là bận rộn và bạn muốn giết 3, đẩy vào 3 viên thuốc độc. Hai chủ đề nhàn rỗi sẽ nhận được yêu cầu tự tử của họ ngay lập tức và hít nó. Các viên thuốc khác vẫn còn trên hàng đợi cho đến khi một thread hoàn thành nhiệm vụ của mình, được liều cuối cùng và chết. Đó là 2 chủ đề - công việc đã hoàn thành! –

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