Ojective-C Programming – Thread

ก็ยังคงต่อเนื่องด้วย Objective-C เหมือนเดิม ว่าจะขึ้น cocoa มา 3 รอบแล้วก็ยังไม่ได้ขึ้นสักที บอกตั้งแต่ iPhone SDK เพิ่งจะออกจนตอนนี้มันเป็น Beta 6 ไปละ หวังไม่โกรธกันนะครับ เอาละวันนี้เข้าเรื่องเลยดีกว่า

Thread

คืออะไร ?  ก็อธิบายได้ง่ายๆว่า มันคือส่วนย่อยๆของ Process หรือชุดคำสั่งนั่นเหละ โดยทำงานแยกจากกัน โดยปกติแล้วโปรแกรมที่เขียนขึ้นมาง่ายๆมักจะเป็นลักษณะ 1 thread หรืออาจจะเรียกได้ว่า “Single Thread” ถ้ามีหลายๆ thread ก็เรียกว่า “Multithread” สำหรับโปรแกรมใหญ่ๆแล้วจะมีการใช้ thread มากกว่า 1

การใช้งาน thread มีข้อดีหลายอย่างตั้งแต่ การใช้ทรัพยาการร่วมกัน การสนับสนุนการทำงานของ multiprocessor และอื่นๆอีกมากมาย สำหรับใครที่ไม่เคยรู้เรื่อง thread นั้นแนะนำให้ไปอ่านเพิ่มเติม

http://www.thaiall.com/os/os04.html

http://en.wikipedia.org/wiki/Thread_(computer_science)

เมื่อพอเข้าใจเบื้องต้นแล้ว เข้าสู้เนื้อหาของเราเลยดีกว่า ว่าเราจะสร้าง Thread และใช้งานมันได้อย่างไร

NSThread

ใน objective-c นั้นสามารถทำได้ตั้งแต่การเรียกใช้ thread ของ ภาษา c เช่น pthread แต่ไหนๆเราจะเขียน objective-c กันแล้ว จะไปใช้ pthread กันทำไม เพราะใน objective-c เองนั้นก็มี class ที่อำนวยความสะดวกในการสร้าง thread อยู่แล้วนั่นคือ NSThread

การสร้าง thread นั้นทำได้ อยู่ 2 วิธีคือ

  • เรียกใช้ class method ที่มีชื่อว่า detachNewThreadSelector:toTarget:withObject:
  • [NSThread detachNewThreadSelector:@selector(sampleMethod: ) 
                     toTarget:self withObject:nil];

การสร้าง thread ด้วย detachNewThreadSelector แบบนี้เราไม่จำเป็นต้องประกาศ ตัวแปรอะไรเลย จะมีก็แต่ parameter ที่ต้องใส่เข้าไป มาดูการประกาศ thread อีกแบบกันเลย

  • สร้าง NSThread ขึ้นมาแล้วเรียก start
    NSThread* myThread = [[NSThread alloc] initWithTarget:self
            selector:@selector(myThreadMainMethod: )
            object:nil];
    [myThread start];

ส่วนแบบวิธีนี้ต่างจากอันแรกคือ เราสร้างตัวแปร NSThread ขึ้นมาแล้วก็ใส่ค่า parameter ให้กับมัน หลังจากนั้นก็เรียก start สำหรับการสร้างแบบนี้มีข้อดี ต่างจากอย่างแรกก็คือ เราสร้างไว้ก่อนแล้วค่อยเรียกให้ thread start ทีหลังได้

ก็หลังจากสร้างเป็นแล้ว มาดูการใช้งาน กันเลยดีกว่า

สมมติว่า เราจะเขียนโปรแกรม ที่เอาไว้เขียน console แบบง่ายๆกัน แต่เป็นแบบ หลายๆ thread

 
#import <Foundation/Foundation.h>
 
//--------------------------------------
// Console Class
//--------------------------------------
 
 
 
@interface Console : NSObject { }
- (void) PrintToConsole : (NSString*) text;
@end
 
@implementation Console
- (void) PrintToConsole : (NSString*) text;
{  
   for ( int i = 0 ; i < 5 ; i++ )  
   {   
        printf("%d: ",i); 
        NSLog(text);  
   }
} 
 
@end
 
//--------------------------------------
// MAIN PROGRAM
//--------------------------------------
 
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
 
Console *sampleConsole = [[Console alloc] init];
Console *helloConsole =  [[Console alloc] init];
Console *okConsole =     [[Console alloc] init];
 
NSThread *helloThread = [[NSThread alloc] initWithTarget:helloConsole 
                        selector:@selector(PrintToConsole: ) 
                        object:@"Hello"];
 
[NSThread detachNewThreadSelector:@selector(PrintToConsole: )  
                                  toTarget:sampleConsole 
                                  withObject:@"Sample"];     
 
[NSThread detachNewThreadSelector:@selector(PrintToConsole: )  
                                  toTarget:okConsole 
                                  withObject:@"Ok"];
 
NSLog(@"\nHello, World From Main thread!\n");
[helloThread start];  
 
[NSThread sleepForTimeInterval:1];           
[pool drain];
return 0;
}

จากโปรแกรม ข้างบน อธิบายคร่าวๆนะครับว่า เราประกาศ  class ชื่อ Console ขึ้นมาโดยที่มี method  เพียงแค่ PrintToConsole โดยการทำงานของ method นี้ก็คือให้พิมพ์ข้อความทั้งหมด 5 ครั้ง

หลังจากนั้นก็ทำการประกาศ ตัวแปร Console ขึ้นมาอีก 3 โดยแต่ละ object นั้นก็ทำการพิมพ์ข้อความออกมาไม่เหมือนกันนั่นก็คือ Hello , Sample , Ok

ลำดับต่อไปก็ทำการ สร้าง Thread ขึ้นมา โดยจากตัวอย่าง ผมได้ทำการใช้วิธีการประกาศทั้ง 2 แบบคือทั้งแบบ ประกาศ ตัวแปร NSThread ขึ้นมาก่อน แล้วค่อยเรียก Start และอีกแบบก็คือไม่ต้องประกาศ ตัวแปร แต่เรียกใช้ class method ขึ้นมาเลย

ยังมีอีก method ที่น่าสนใจก็คือ sleepForTimeInterval โดยทำหน้าที่คือ หยุดการทำงานตามเวลาที่กำหนด สาเหตุก็เพราะว่า เนื่องจากว่า Thread นั้นทำงานแยกกัน อาจจะมีบาง Thread ที่ทำงานเสร็จก่อน หรือเสร็จหลัง Main Program ก็ได้ ฉนั้นจึงทำการรอให้ แต่ละ Thread ทำงานเสร็จก่อน แล้วค่อยจบโปรแกรม เพื่อที่ว่าป้องกันปัญหา Memory Leak หรือโปรแกรมทำงานผิดพลาด

และหลังจาก compile และ run แล้วผลลัพธ์ที่ได้ จะได้ประมาณแบบนี้

0: 0: 2008-06-09 00:52:54.252 Thread[724:1103] Sample
1: 2008-06-09 00:52:54.253 Thread[724:1203] Ok
1: 2008-06-09 00:52:54.252 Thread[724:10b]
Hello, World From Main thread!
0: 2008-06-09 00:52:54.258 Thread[724:1103] Sample
2: 2008-06-09 00:52:54.258 Thread[724:1203] Ok
2: 2008-06-09 00:52:54.259 Thread[724:2c03] Hello
1: 2008-06-09 00:52:54.260 Thread[724:1103] Sample
3: 2008-06-09 00:52:54.260 Thread[724:1203] Ok
3: 2008-06-09 00:52:54.261 Thread[724:2c03] Hello
2: 2008-06-09 00:52:54.262 Thread[724:1103] Sample
4: 2008-06-09 00:52:54.262 Thread[724:1203] Ok
4: 2008-06-09 00:52:54.263 Thread[724:2c03] Hello
3: 2008-06-09 00:52:54.264 Thread[724:1103] Sample
2008-06-09 00:52:54.265 Thread[724:1203] Ok
2008-06-09 00:52:54.265 Thread[724:2c03] Hello
4: 2008-06-09 00:52:54.267 Thread[724:2c03] Hello

จะเห็นว่า ลำดับ แต่อันจะไม่เรียงกัน อาจจะสลับกันไปมา ก็เนื่องจากว่า Thread แต่ละอันนั้นทำงานแยกจากกัน

*** ข้อระวังสำหรับการใช้ Thread ใน Objective-C ***
ใน Thread ถ้ามีการประกาศตัวแปร หรือว่าใช้งานในลักษณะที่ต้องมีการจอง memory ต้องมี NSAutoreleasePool ด้วยเสมอ

เช่น

- (void) PrintToConsole : NSString* text  ถ้าเราจะเปลี่ยน code การทำงานให้มันพิมพ์ออกที่หน้า console เหมือนกันแต่เราไม่อยากใช้ NSLog ก็อาจจะเขียนใหม่ได้ว่า

- (void) PrintToConsole: (NSString*) text;
{ 
   for ( int i = 0 ; i < 5 ; i++ ) 
   { 
     printf("%d: %s ",i, [text UTF8String]);
   }
}

การเขียนแบบนี้เวลา run จะเกิด error น่ะครับ เพราะว่า [text UTF8String] มันจะไปจอง memory ใหม่แล้วส่งค่า char* กลับมา ฉนั้น ถ้าไม่มี NSAutoreleasePool มันจะ error

การเขียนที่ถูกต้องควรจะเป็น

- (void) PrintToConsole: (NSString*) text;
{ 
  NSAutoreleasePool * threadPool = [[NSAutoreleasePool alloc] init];
  for ( int i = 0 ; i < 5 ; i++ )  
  {   
    printf("%d: %s ",i, [text UTF8String]);
  }  
  [threadPool drain];
}

สำหรับ tutorial นี้จริงๆผมกะว่าจะเป็น guide ง่ายๆสำหรับคนที่รู้เรื่อง thread อยู่แล้ว ก็ถ้าใครยังไม่เข้าใจ ต้องการรู้เรื่องเกี่ยวกับ thread เพิ่มมากขึ้นก็แนะนำให้ไปอ่าน link ที่ข้างบน แล้วก็ค้นหาจาก google ก็ได้ครับ

ครั้งหน้า ก็ยังอยู่กับ objective-c น่ะครับ ส่วน source สำหรับ วันนี้ก็โหลดได้ที่นี่เลย

Technorati Tags: , , , ,


4 responses so far, want to say something?

  1. Avatar

    say_hi says:

    ช่วยแนะนำหนังสือเกี่ยวกับ Objective-C แบบ Intermediate and Advance ดีให้หน่อยครับ อยากได้ครับ

  2. Avatar

    say_hi says:

    ขอบคุณครับ เล็งๆไว้ว่าจะสั่ง Amazon นะครับ
    เก็บไว้เป็น Reference ครับ ตอนนี้พยายามตระเวณเอา
    กับฝาหรั่งใน Web พยายามดูดๆๆ ดูดซับอะครับ อยากให้
    คนไทยเป็นแบบฝรั่งนะครับ คือความรู้ก็ Share กานนะครับ
    เอาแบบสนุกๆ ใจรักครับ เงินตามมาเองเนอะ

  3. Avatar

    skyper_v says:

    งง มานานนมากก
    กับ @selector Method ช่วย เคลียให้ผมที
    ว่ามันเป็นยังไง ใช้ยังไง
    ผมลองใช้แล้ว ได้บ้าง ไม่ได้บ้าง

    มันคล้่ายๆแตก thread ไปทำปะครับ
    แต่ก็งงๆกับการส่ง parameter ไปให้กับตัว @selector อยู่ครับ

  4. Avatar

    admin says:

    จริงๆแล้ว selector มันคือ pointer to function นะครับ ดูตัวอย่าง code ต่อไปนี้

    @interface Student : NSObject {}
    -(void) setName:(NSString*)name;
    @end

    สมมติว่าเราต้องการให้ ใส่ชื่อนักเรียน ถ้าหากเขียน แบบปกติก็จะได้ว่า
    [student setName::@"Ter"];

    ถ้าหากเขียนแบบใช้ selector ก็จะได้ว่า
    [student performSelector:@selector(setName:) withObject:@"Ter"];

    ————————

    ฉนั้นแล้วคำถามว่า มันคล้ายๆกับการแตก thread หรือเปล่า สรุปง่ายๆคือไม่ใช่การสร้าง แต่เป็นการบอกให้ thread ที่จะสร้างรู้ว่าจะไปทำงาน function ไหน )

    [NSThread detachNewThreadSelector:@selector(PrintToConsole: )
    toTarget:sampleConsole
    withObject:@"Hello"];

    จาก code ข้างบนเราสร้าง thread ขึ้นมาแล้วหลังจากนั้นเราก็ใช้ pointer ( selector ) ไปยัง function ที่ชื่อ PrintToConsole: โดยส่ง “Hello” ให้กับ thread เอาไปใช้ในฟังชั่น PrintToConsole ไงครับ

Leave a Reply

You must be logged in to post a comment.