Đa luồng là một công cụ mạnh mẽ để tạo các ứng dụng hiệu suất cao, đặc biệt là các ứng dụng có yêu cầu tƣơng tác với ngƣời dùng hoặc làm việc với các dịch vụ và tài nguyên phân tán. Microsoft. NET đã phá vỡ những rào cản đó từng tồn tại trong việc tạo ra các ứng dụng đa luồng.
3.2.2.1 Khái niệm về luồng(thread)
Để hiểu đƣợc đa luồng đầu tiên chúng ta phải hiểu các luồng. Mỗi ứng dụng / chƣơng trình đang chạy trên một hệ thống là một tiến trình. Tiến trình cho mỗi
chƣơng trình bao gồm một hoặc nhiều luồng mà bộ xử lý cần một thời gian nhất định để thực hiện. Mỗi luồng có chứa tất cả các thông tin ngữ cảnh đƣợc yêu cầu bởi hệ thống để thực hiện các lệnh của chƣơng trình.
Hệ điều hành có trách nhiệm lập kế hoạch và thực hiện các luồng. Hãy nhớ những ngày của Windows 3.x? Một tiến trình luồng duy nhất có thể, và thƣờng làm, độc quyền tất cả các thời gian xử lý. Hệ thống sẽ chỉ ngồi và chờ đợi cho luồng đó hoàn thành trƣớc khi thực hiện bất kỳ tiến trình khác.
Các hệ điều hành mới hơn, chẳng hạn nhƣ Windows 2000, hỗ trợ đa nhiệm chặn trƣớc nó phân bổ mỗi luồng một lát thời gian. Khi thời lát gian của luồng hiện đang thực hiện đã trôi qua, luồng bị đình chỉ bởi hệ điều hành, bối cảnh của luồng đƣợc lƣu, bối cảnh của luồng khác đƣợc tải, và luồng khác thì tiếp tục thực hiện theo trạng thái trƣớc đó. Điều này đem đến sự xuất hiện nhiều luồng đƣợc thực hiện đồng thời và ngăn chặn hệ thống trở thành không phản hồi từ một luồng đơn (kết thúc nhiệm vụ bất cứ ai?). Trên các hệ thống có hơn một bộ xử lý, các luồng đƣợc phân bổ qua tất cả các bộ xử lý nhƣ vậy thực sự có nhiều luồng đang đƣợc thi hành tại cùng một thời điểm.
3.2.2.2 Căn bản về mô hình luồng
Single Thread - Đơn luồng có nghĩa là chỉ có một luồng trong một tiến trình và
nó đang làm tất cả các công việc cho tiến trình này. Tiến trình phải chờ đợi thực thi hiện tại của luồng hoàn thành trƣớc khi nó có thể thực hiện một hành động khác.
Kết quả đơn luồng ở thời gian nhàn rỗi của hệ thống và sự thất vọng của ngƣời dùng. Ví dụ, giả sử chúng ta đang lƣu một tệp tin vào một mạng từ xa bằng cách sử dụng một ứng dụng đơn luồng. Vì chỉ có một luồng duy nhất trong ứng dụng, ứng dụng sẽ không thể làm bất cứ điều gì khác trong khi tệp tin đang đƣợc lƣu giữ vào vị trí từ xa. Vì vậy ngƣời dùng chờ đợi và bắt đầu tự hỏi, liệu ứng dụng bao giờ sẽ tiếp tục.
Apartment Threading (Single Threaded Apartment – căn hộ đơn luồng)
Apartment threaded - Căn hộ luồng có nghĩa là có nhiều luồng trong ứng dụng. Trong căn hộ luồng đơn (single thread apartment - STA) mỗi luồng bị cô lập trong một căn hộ riêng biệt bên dƣới tiến trình. Tiến trình qui định khi nào và trong bao lâu luồng trong mỗi căn hộ có thể thi hành. Mọi yêu cầu đƣợc xếp hàng thông qua hàng đợi thông điệp của Windows, nhƣ vậy chỉ có một căn hộ luồng duy nhất đƣợc truy cập tại một thời điểm và do đó chỉ có một luồng đơn sẽ đƣợc thực hiện tại một thời điểm. STA là mô hình luồng mà hầu hết các nhà phát triển Visual Basic quen thuộc vì đây là mô hình luồng có sẵn cho các ứng dụng VB trƣớc khi có VB.NET. Bạn có thể nghĩ nó nhƣ một hàng các căn hộ một phòng mà có thể truy cập tới một tại một thời điểm thông qua một hành
lang duy nhất. Ƣu điểm của nó so với đơn luồng là nhiều lệnh có thể đƣợc ban hành cùng một lúc thay vì chỉ một lệnh duy nhất, nhƣng các lệnh vẫn tuần tự thực hiện.
Free Threading (Multi Threaded Apartment – căn hộ đa luồng)
Các ứng dụng luồng tự do giới hạn trong các ngôn ngữ nhƣ C++ cho tới khi Microsoft.NET ra đời. Mô hình Luồng tự do/Multi Threaded Apartment (MTA) có duy nhất một căn hộ đơn đƣợc tạo gia bên dƣới tiến trình chứ không phải nhiều căn hộ. Một căn hộ đơn chứa đựng nhiều luồng hơn là chỉ một luồng. Không cần hàng đợi thông điệp bởi vì tất cả các luồng là một phân của cùng một căn hộ và có thể chia sẻ dữ liệu mà không cần proxy. Bạn có thể nghĩ nó nhƣ là một công trình với nhiều phòng mà đều có thể vào đƣợc một khi bạn đã ở bên trong. Các ứng dụng này thƣờng thực hiện nhanh hơn so với đơn luồng và STA vì có ít hệ thống hơn và có thể đƣợc tối ƣu hóa để loại bỏ thời gian nhàn rỗi của hệ thống.
Các loại ứng dụng này là phức tạp hơn khi lập trình. Ngƣời phát triển phải cung cấp sự đồng bộ hóa giữa các luồng nhƣ một phần của mã lệnh để chắc chắn các luồng không đồng thời truy cập tới cùng các nguồn tài nguyên. Một điều kiện đƣợc biết đến nhƣ trƣờng hợp tranh đua có thể xuất hiện khi luồng truy cập tới tài nguyên chia sẻ và thay đổi tài nguyên tới một trạng thái không hợp lệ và khi luồng khác truy cập tới tài nguyên và sử dụng nó trong điều kiện không hợp lệ trƣớc khi một luồng khác trả tài nguyên về trạng thái hợp lệ. Vì vậy việc cần thiết đặt một khóa vào tài nguyên để ngăn chặn các luồng khác truy cập cho tới khi khóa đƣợc bỏ đi. Tuy nhiên, điều này có thể dẫn đến tình huống bế tắc nơi hai luồng cạnh tranh về tài nguyên mà không cái nào xử lý đƣợc. Ví dụ, luồng #1 có một tài nguyên bị khóa và đang chờ tài nguyên khác đang khóa bởi luồng #2. Luồng #2 lại xuất hiện tình trạng đang chờ đợi tài nguyên bị khóa bởi luồng #1. Nhƣ vậy, cả hai luồng đều đang chờ đợi lẫn nhau và không luồng nào sẽ đƣợc cho phép xử lý. Chỉ có một con đƣờng để tránh tình trạng trên là thông qua thiết kế và kiểm thử tốt.
3.2.3 Cấu trúc và Giải thuật
Ứng dụng đƣợc xây dựng với 3 thành phần chính:
- UI – User Interface (giao diện ngƣời dùng) là các trang web hiển thị giao diện tìm kiếm, kết quả và các thông tin khác. Từ giao diện này ngƣời dùng chọn lựa danh sách cơ sở dữ liệu cần tìm, đƣa vào câu truy vấn và gửi tới ZetMonitor – Bộ quản lý các luồng Z39.50 client. UI cũng nhận kết quả trả về và các thông tin khác để hiển thị cho ngƣời dùng.
- ZetMonitor – Bộ quản lý luồng Z39.50 client. ZetMonitor nhận câu lệnh truy vấn và danh sách cơ sở dữ liệu đích từ tầng UI. Khởi tạo các luồng
Z39.50 client, mỗi luồng làm việc với 1 cơ sở dữ liệu. Số lƣợng các luồng đƣợc qui định bằng kích thƣớc mảng các luồng Z39.50 client theo cấu hình đảm bảo hệ thống hoạt động hiệu quả. Mỗi luồng khi gặp lỗi hoặc hoàn thành nhiệm vụ sẽ đƣợc loại bỏ khỏi mảng các luồng và một luồng khác làm việc với cơ sở dữ liệu mới sẽ đƣợc đƣa vào cho đến khi hết danh sách cơ sở dữ liệu đích, hoặc lệnh hủy ZetMonitor đƣợc gọi. ZetMonitor quản lý một mảng kết quả trả về từ phía các luồng Z39.50 client và cung cấp theo yêu cầu của tầng UI. Mảng kết quả trả về không có độ dài cố định, nó sẽ đƣợc mở rộng khi ngƣời dùng duyệt tới gần cuối mảng.
- ZetThread – Luồng Z39.50 client là một Z39.50 client, khi đƣợc khởi tạo nhận câu truy vấn và thông tin cơ sở dữ liệu đích từ ZetMonitor. Khi chạy sẽ kết nối và gửi yêu cầu đến Z39.50 server. Nếu gặp lỗi sẽ gửi tình trạng đến ZetMonitor để ZetMonitor loại bỏ ZetThread này khỏi mảng và thêm ZetThread khác vào nếu có. ZetThread nhận thông tin số biểu ghi trả về từ Z39.50 server, thực hiện tải về bản ghi lần lƣợt. Với mỗi bản ghi tải về xong ZetThread cố gắng gửi tới ZetMonitor, nếu đƣợc ZetMonitor chấp nhận nó sẽ lấy bản ghi tiếp và gửi cho ZetMonitor, nếu không, nó nằm nghỉ một lát và gửi lại. Cứ nhƣ vậy cho đến khi hết kết quả hoặc nó bị hủy đi.
Việc xây dựng một lƣợc đồ cho loại ứng dụng đa luồng gặp nhiều khó khăn do tính chất phức tạp của các trạng thái. Do vậy tôi cố gắng đƣa ra mô tả về cấu trúc và hoạt động của các thành phần chƣơng trình nhƣ trên.
3.2.4 Sơ đồ lớp và mã nguồn chính 3.2.4.1 Class Diagram
Hình 4: Mô hình lớp
3.2.4.2 Lớp ThreadHelper
Lớp ThreadHelper giúp khởi tạo và quản lý luồng đƣợc kế thừa trong lớp ZetThread
using System;
using System.Threading; namespace ZetPortal {
public class ThreadHelper
{
protected Thread threadField; public ThreadHelper()
{
threadField = new Thread(new ThreadStart(Run)); }
public ThreadHelper(String name) {
threadField = new Thread(new ThreadStart(Run)); threadField.Name = name;
}
public virtual void Run() {
}
public virtual void Start() {
threadField.Start(); }
public virtual void Interrupt() {
threadField.Interrupt(); }
public virtual void Abort() {
threadField.Abort(); }
public virtual void Join() {
threadField.Join(); }
public virtual void Join(int miliseconds) {
threadField.Join(miliseconds); }
public bool IsAlive {
get { return threadField.IsAlive; } }
public bool IsBackground {
get { return threadField.IsBackground; }
set { if (threadField.IsAlive) threadField.IsBackground = value; }
} } }
3.2.4.3 Lớp ZetThread
Kế thừa từ lớp ThreadHelper để khởi tạo một Z39.50 client hoạt động nhƣ một luồng -Thread. using System; using CSZOOMC; using System.Threading; using MarcXmlParser; namespace ZetPortal {
public class ZetThread : ThreadHelper
{
private ZoomConnection _zc; private ZoomQuery _zqry; private ZoomResultSet _zrs; public event Thread_Done Done;
public event Thread_Send_Record Send_Record; public event Thread_Send_FoundCount Send_Size; public event Thread_Status_Changed Status_Changed; private int _iReceivedCount = 0;
private int _iFoundCount = 0; private ZDb _DbInfo = new ZDb(); private string _strQuery;
private int _index=0;
public ZetThread(int index,String sName,String sDbInfo, String
strQuery): base(sName) {
//string Name, string Host,int Port,string Database,string Charset
_index = index; // indentifier of the thread in thread array of Monitor _DbInfo.load_Xml(sDbInfo); _strQuery= strQuery; } ~ZetThread() { try { if (threadField.IsAlive) { threadField.Abort(); threadField.Join(); } _zrs = null; _zc = null; }
catch(Exception ex) { //do nothing String errorString=ex.Message; } finally { //do nothing } }
public override void Run() {
try {
createConnection();
_zrs = _zc.Search(_zqry); int count = _zrs.GetSize();
if ((count > 0) && (_iFoundCount == 0)) {
Send_Size(count); _iFoundCount = count; }
int start = _iReceivedCount;
Status_Changed(_index, "Running", _iReceivedCount, _iFoundCount);
for (int i = start; i < count; i++) {
ZoomRecord zr = _zrs.GetRecord(i); String xmlRecord = string.Empty; CRecord myRec = new CRecord();
if (_DbInfo.Charset.ToLower().Trim() == "a") {
xmlRecord = zr.GetXMLRecord(_DbInfo.Charset); }
else //auto detect charset
{
xmlRecord = zr.GetXMLRecord(); }
myRec.load_Xml(xmlRecord);
{
myRec.Datafields.Clean(); myRec.Datafields.Refresh(); }
CDatafield Df = new CDatafield(); Df.Tag="999";
CSubfield Sf = new CSubfield(); Sf.Code="a";
Sf.Value = _DbInfo.Name; Df.Subfields.Add(Sf); myRec.Datafields.Add(Df); xmlRecord = myRec.OuterXml; int icheck = _iReceivedCount; Send_Record(this, xmlRecord);
while (icheck == _iReceivedCount) //while record has not been added to ResultCache
{ if (threadField.IsAlive) { Thread.Sleep(500); Send_Record(this, xmlRecord); } else { break; } }
Status_Changed(_index, "Running", _iReceivedCount, _iFoundCount);
}
if (_iFoundCount == 0) {
Status_Changed(_index, "Not found", _iReceivedCount, _iFoundCount);
} else {
Status_Changed(_index, "Finished", _iReceivedCount, _iFoundCount);
}
//Done(this);
}
catch (CSZOOMCException czex) {
Status_Changed(_index, czex.Message, _iReceivedCount, _iFoundCount);
//Done(this);
}
catch (ThreadAbortException) {
Status_Changed(_index, "Abort", _iReceivedCount, _iFoundCount);
}
catch (Exception ex) { Status_Changed(_index,ex.Message, _iReceivedCount, _iFoundCount); //Done(this); } finally { Done(this); } }
public int FoundCount { get { return _iFoundCount; } set { _iFoundCount = value; } }
public int ReceivedCount { get { return _iReceivedCount; } set { _iReceivedCount = value; } }
public int Index { get { return _index; } }
public string Name { get { return _DbInfo.Name; } }
private void createConnection() {
ZoomFactory zf = new ZoomFactory();
_zc = zf.CreateZoomConnection(_DbInfo.Host, _DbInfo.Port); _zqry = zf.CreateZoomQuery(_strQuery, 0); if (_DbInfo.Name != "") { _zc.SetOption("user", _DbInfo.UserID); _zc.SetOption("pass", _DbInfo.Password); } _zc.SetOption("databaseName", _DbInfo.Database); _zc.SetOption("preferredRecordSyntax", "USmarc"); _zc.SetOption("elementSetName", "F"); } } } 3.2.4.4 Lớp ZetMonitor
Lớp ZetMonitor nhận câu lệnh truy vấn và danh sách các cơ sở dữ liệu đích Z39.50 từ giao diện ngƣời dùng, khởi tạo và quản lý các luồng Z39.50 client, quản lý bộ đệm kết quả và nhận kết quả trả về từ các luồng Z39.50 client.
using System; using System.Collections; using System.Threading; using MarcXmlParser; namespace ZetPortal {
public sealed class ZetMonitor
{
public event Monitor_Search_Finished Search_Done; public event Monitor_First_Record_Found First_Found; public event Monitor_Send_Status Send_Status; private int _iCacheSize = 50; private int _iCacheStepSize = 25; private int _iMaxThreadArraySize =15; private int _iLastDatabaseIndex = 0;
private Hashtable _ResultRecords=new Hashtable();
private ArrayList _ThreadArray = new ArrayList(); private int _iTotalFoundCount = 0; private int _iTotalReceivedCount = 0; private Hashtable _ZDbRecords = new Hashtable(); private String _RPNQuery = String.Empty; private bool _isDisposing = false; private int _index=0; public int index { set { _index=value; } get { return _index; } } public ZetMonitor() { } ~ZetMonitor() {
Object obj = _ThreadArray; lock (obj) {
if (_ThreadArray.Count > 0) {
for (int i = 0; i < _ThreadArray.Count; i++) {
ZetThread JThread = _ThreadArray[i] as ZetThread; if (JThread.IsAlive) { JThread.Abort(); JThread.Join(500); } } } } }
public int CurrentCacheSize {
get {
lock(obj) { return _iCacheSize; } } }
public int LastDatabaseIndex {
get {
Object obj = _iLastDatabaseIndex; lock (obj) { return _iLastDatabaseIndex; } } }
public int CurrentThreadCount {
get {
Object obj = _ThreadArray; lock(obj) { return _ThreadArray.Count; } } }
public int CacheSize {
get {
Object obj = _iCacheSize; { return _iCacheSize; } } set {
Object obj = _iCacheSize; lock(obj) { _iCacheSize = value; } } }
public int CacheStepSize { get { Object obj=_iCacheStepSize; lock (obj) { return _iCacheStepSize; } } set {
Object obj = _iCacheStepSize; lock(obj)
{
_iCacheStepSize = value; }
}
}
public int MaxThreadArraySize {
get {
Object obj = _iMaxThreadArraySize; lock(obj) { return _iMaxThreadArraySize; } } set {
Object obj = _iMaxThreadArraySize; lock(obj) { _iMaxThreadArraySize = value; } } }
public String ZDbRecordsXml {
set {
Object obj = _ZDbRecords; _ZDbRecords.Clear(); lock (obj) {
CRecords DbInfos = new CRecords(); DbInfos.load_Xml(value); for (int i = 0; i < DbInfos.Count; i++) { _ZDbRecords.Add(i, DbInfos.Record(i).OuterXml); } } } get { lock (this) {
CRecords DbInfos = new CRecords(); for (int i = 0; i < _ZDbRecords.Count; i++) {
CRecord myRec = new CRecord(); myRec.load_Xml(_ZDbRecords[i].ToString()); DbInfos.Add(myRec); } return DbInfos.OuterXml; } } } public String RPNQuery {
get {
Object obj = _RPNQuery; lock (obj) { return _RPNQuery; } } set
{
Object obj = _RPNQuery; lock(obj) { _RPNQuery = value; } } }
public void Start() {
//initialize default value
lock (this) { _iTotalFoundCount = 0; _iTotalReceivedCount = 0; _isDisposing = false; _ResultRecords.Clear();
//Decide SafeThreadArray Length
_iLastDatabaseIndex = _ZDbRecords.Count >
_iMaxThreadArraySize ? _iMaxThreadArraySize : _ZDbRecords.Count; for (int i = 0; i < _iLastDatabaseIndex; i++) {
ZetThread JThread; ZDb dbInfo = new ZDb();
dbInfo.load_Xml(_ZDbRecords[i].ToString()); JThread = new ZetThread(i, dbInfo.Name, dbInfo.OuterXml, _RPNQuery);
JThread.Send_Record += new
Thread_Send_Record(JThread_Send_Record);
JThread.Done += new Thread_Done(JThread_Done); JThread.Send_Size += new Thread_Send_FoundCount(JThread_Send_Size); JThread.Status_Changed += new Thread_Status_Changed(JThread_Status_Changed); _ThreadArray.Add(JThread); }
for (int i = 0; i < _ThreadArray.Count; i++) {
(_ThreadArray[i] as ZetThread).Start(); }
} }
public void JThread_Send_Record(ZetThread sender, String strValue) {
lock (this) {
if (!_isDisposing) {
int count = _ResultRecords.Count; if (count < _iCacheSize) { _ResultRecords.Add(count, strValue); sender.ReceivedCount++; count++; _iTotalReceivedCount = count; if(count==1) {
// raise a first found event
if (First_Found != null) { First_Found(this); } } if (Send_Status != null)
{ Send_Status(sender.Name + " Found = " + sender.FoundCount.ToString() + " Received=" + sender.ReceivedCount.ToString()); } } } } }
public void JThread_Status_Changed(int index, string message, int ReceivedCount, int FoundCount) {
Object obj = _ZDbRecords; lock (obj) { if (index < _ZDbRecords.Count) { ZDb dbInfo = new ZDb(); dbInfo.load_Xml(_ZDbRecords[index].ToString()); if (Send_Status != null) { Send_Status(dbInfo.Name + ": " + message); } dbInfo.StatusMessage = message; dbInfo.ReceivedCount = ReceivedCount; dbInfo.FoundCount = FoundCount; _ZDbRecords[index] = dbInfo.OuterXml; } } }
public void Dispose() {
lock (this) {
_isDisposing = true; for (int i = 0; i < _ThreadArray.Count; i++) {
ZetThread JT = _ThreadArray[i] as ZetThread; if (JT != null) { if (JT.IsAlive) { JT.Abort(); JT.Join(500); } } } _ThreadArray.Clear(); _iLastDatabaseIndex = 0; _ResultRecords.Clear(); _iTotalFoundCount = 0; _iTotalReceivedCount = 0; } }
private void JThread_Done(ZetThread DoneThread) {
lock (this) {
if ((!_isDisposing) &&
(_ThreadArray.Count<_iMaxThreadArraySize)) //prevent add new thread while dispose call { if (_iLastDatabaseIndex < _ZDbRecords.Count) { ZDb dbInfo = new ZDb(); dbInfo.load_Xml(_ZDbRecords[_iLastDatabaseIndex].ToString()); ZetThread JThread = new
ZetThread(_iLastDatabaseIndex, dbInfo.Name, dbInfo.OuterXml, _RPNQuery); JThread.Send_Record += new Thread_Send_Record(JThread_Send_Record); JThread.Done += new Thread_Done(JThread_Done); JThread.Send_Size += new Thread_Send_FoundCount(JThread_Send_Size); JThread.Status_Changed += new Thread_Status_Changed(JThread_Status_Changed); _ThreadArray.Add(JThread); _iLastDatabaseIndex++; JThread.Start(); } else if((_iLastDatabaseIndex ==
_ZDbRecords.Count) && (_ThreadArray.Count==0))//end of database list raise Finish the search session event
{ if (Search_Done != null) { Search_Done(this); } } } } }
private void JThread_Send_Size(int count) {
Object obj = _iTotalFoundCount; lock (obj)
{
_iTotalFoundCount += count; }
}
public String get_Record(int i) { if (!_isDisposing) { if (i >= _iTotalReceivedCount) return ""; else {
if (i >= (_iCacheSize - _iCacheStepSize) && (_iTotalFoundCount > _iCacheSize))
{
Object obj = _iCacheSize; lock (obj)
{
_iCacheSize += _iCacheStepSize; }
}
Object obj1 = _ResultRecords; lock (obj1)
{
return _ResultRecords[i].ToString(); }
} } { return ""; } }
public int TotalFoundCount {
get {
Object obj = _iTotalFoundCount; lock (obj) { return _iTotalFoundCount; } } }
public int TotalReceivedCount {
get {
Object obj = _iTotalReceivedCount; lock (obj) { return _iTotalReceivedCount; } } }
public bool IsAllThreadDead {
get {
Object obj = _ThreadArray; lock(obj) { return (_ThreadArray.Count == 0); } } } } } 3.3 Thử nghiệm 3.3.1 Cấu hình Cấu hình ứng dụng
Ứng dụng đƣợc xây dựng trên công nghệ ASP.NET 2.0 ngôn ngữ C#. Cần cài đặt trên hệ điều hành Windows 2003/2008 server với máy chủ web IIS 6.0 trở lên và Microsoft NET Framework 2.0 hoặc 3.x.
Sao chép chƣơng trình vào máy chủ và đăng ký thƣ mục ứng dụng web