Ojective-C Programming – Thread II

หลังจากเขียน Thread พอเป็นแล้วก็ยังมีเรื่องเกี่ยวกับ Thread ที่ต้องรู้อีก อยู่อย่างหนึ่งก็คือ การ Synchronizes ระหว่าง Thread

อย่างที่บอกไปแล้ว ว่าการเขียนโปรแกรมโดยการใช้ Thread นั้นมีข้อดีมากมาย แต่ก็มีข้อเสียเหมือนกัน และหนึ่งในข้อเสียนั้นก็คือการเขียนโปรแกรมแบบที่มีหลายๆ thread นั้นทำให้โปรแกรมของเราซับซ้อนมากขึ้น และลำบากต่อการจัดการข้อมูลที่มีการใช้งานร่วมกันระหว่าง thread เช่นเป็นต้นว่า

ถ้าโปรแกรมที่เราเขียนขึ้นมีหลายๆ Thread โดยแต่ละ Thread นั้นมีการแชร์ข้อมูลร่วมกัน ถ้าหากเราไม่ได้เขียนโปรแกรมให้มันมีการควบคุมการทำงานของแต่ละ Thread หรือเขียนได้ไม่ดี อาจจะเกิดเหตการณ์ในลักษณะแบบนี้

สมมติว่า โปรแกรม ของเรามี 2 thread ที่มีการใช้ตัวแปรร่วมกันคือ Money และแต่ละ thread นั้นก็จะทำการอ่านค่า Money และเขียนผลลัพธ์ที่ได้จากการคำนวนใหม่ลงไป

  1. Thread A : อ่านตัวแปร Money ได้มีค่าเป็น 10$ และระหว่างการคำนวนนี้ ใช้เวลาประมาณ 10 นาโนวินาที
  2. และระหว่างที่ A กำลังคำนวนอยู่นั้น Thread B ก็ได้อ่านค่า Money เหมือนกัน ได้ค่า 10$ เหมือนกัน แต่ใช้เวลาในการคำนวนเพียงแค่ 5 นาโนวินาที
  3. เนื่องจาก ว่า B นั้นทำงานเสร็จก่อน A ถึง 5 นาโนวินาที. Thread B จึงทำการ เขียนค่า Money เข้าไปใหม่เป็น 30$
  4. เมื่อ A ทำงานเสร็จ A ก็เลยเขียนค่า Money ที่ได้จากการคำนวนใหม่เป็น 15$

**** จะเห็นว่าค่าผลลัพธ์ที่ได้นั้น มันเกิดการ เขียนทับกันขึ้นมา ลองจินตนาการว่า สมมติว่า มี คนสองคนทำการฝากเงิน ในเวลาพร้อมๆกัน เข้าบัญชีเดียวกัน ขณะที่นาย A กำลังฝากเงินต้องใช้เวลาในการประมวลเสร็จใช้เวลา 10 วินาทีและในระหว่างนี้ นาย B ก็ฝากเงินมาเหมือนกัน แต่ใช้เวลา 5 วินาที ถ้าหากเราไม่มีการจัดการตรงนี้ นั่นก็แปรว่า เมื่อข้อมูลในบัญชี update จากการฝากเงินของ B ก็จะถูกเขียนทับด้วย ข้อมูลใหม่ที่นาย A ได้ฝากเข้ามา นันก็แปลว่า ข้อมูลเงินฝากนั้นผิดพลาด สิ่งที่ควรจะเป็นก็คือ ต้องรอให้ A ทำงาน เสร็จก่อน แล้ว B จึงอ่านข้อมูล ****

ลองมาเขียนโปรแกรมสักโปรแกรม โดยโปรแกรมนี้มี 2 Thread โดยที่
thread เป็นการเพ่ิมจำนวน member ใน array, ส่วนอีก thread นั้นทำหน้าที่ดึงข้อมูลจาก array แล้วทำการเปลี่ยนแปลงค่า

ลองดูโปรแกรม ข้างล่างนี้

#import <Foundation/Foundation.h>
// MyArray Class
@interface MyArray : NSObject
{
    NSMutableArray* m_array;
}
- (id) init;
- (void) dealloc;
- (void) AddMember: (NSString*) text;
- (void) AddMoreText: (NSString*) text;
- (void) PrintAllMember;
@end
 
@implementation MyArray
-(id) init
{
    [super init];
    m_array = [[NSMutableArray alloc] init];
    return self;
}
-(void) dealloc
{
    [m_array release];
    [super dealloc];
}
- (void) PrintAllMember
{
    int total = [m_array count];
    for( int i = 0 ; i < total ; i++)
    {
        printf("%d %s\n",i,[[m_array objectAtIndex:i] UTF8String]);
    }
}
 
// Add Member Thread Function
- (void) AddMember: (NSString*) text
{
    for( int i = 0 ; i < 100 ; i++)
    {
        [m_array addObject:[[NSMutableString alloc]initWithString:text]];
    }
}
 
// Add Member Thread Function
- (void) AddMoreText: (NSString*) text
{
    for( int i = 0 ; i < [m_array count] ; i++)
    {
        [[m_array objectAtIndex:i] appendString:text];
    }
}
 
@end
 
// MAIN PROGRAM
int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    MyArray *myarray = [[MyArray alloc] init];
 
    [NSThread detachNewThreadSelector:@selector(AddMember: )
                  toTarget:myarray  withObject:@"Hello"];
    [NSThread detachNewThreadSelector:@selector(AddMoreText: )
                  toTarget:myarray  withObject:@" World!!"];
 
    [NSThread sleepForTimeInterval:5];
    [myarray PrintAllMember];
 
    [pool drain];
 
    return 0;
 
}

และจากการ Compile & Run โปรแกรม ผลลัพธ์ที่ได้จะมีเป็นประมาณนี้

0 Hello World!!
1 hello World!!
.
.
8 Hello World!!
9 Hello World!!
10 Hello World!! World!!
11 Hello World!! World!!
12 Hello World!! World!!
13 Hello World!! World!!
14 Hello World!! World!!
15 Hello World!!
16 Hello World!!
17 Hello World!!
18 Hello
.
.
98 Hello
99 Hello

จากผลลัพธ์จะเห็นว่า เนื่องจาก Thread ทั้งสองคือ thread ที่เรียกใช้งาน AddMember ที่ทำหน้าที่เพิ่มจำนวน member ใน m_array  และ อีก thread ทำหน้าที่ AddMoreText ทำหน้าที่เรียกใช้ ตัวแปร m_array และแก้ไขค่า ในแต่ละ member ของ m_array ซึ่งสอง thread ทำงานแยกจากกันและเริ่มทำงานพร้อมกัน

ดังนั้น ในขณะที่ Thread ทั้งสองทำงานและมีการใช้งานข้อมูลร่วมกัน โดยไม่มีการ synchronize ข้อมูลระหว่างกันว่า m_array ว่าตอนนี้ข้อมูลเป็นอย่างไร จึงเป็นเหตุทำให้โปรแกรมทำงานผิดพลาดได้ ( มันควรจะเป็น Hello World!! ทั้งหมด ไม่ใช่ Hello World!! World!! หรือ Hello อย่างเดียว )

วิธีการแก้ปัญหา หรือป้องกัน สิ่งที่เกิดขึ้น มักนิยมใช้ คือ การ Lock ตัวแปร หรือ code ให้ทำงานได้ทีละ Thread เพื่อกันไม่ให้ Thread อื่นๆเข้าใช้จนกว่าจะใช้งานเสร็จ

NSLock

เราจะใช้ class ที่ชื่อ NSLock เพื่อจะเข้ามาช่วยในการจัดการ การทำงานของแต่ละ thread โดยมันจะทำหน้าที่ lock code ในส่วนที่ต้องการให้สามารถเข้าทำงานได้ทีละ Thread ไว้ ส่วน thread อื่นๆ ก็ต้องทำการรอ จนกว่า จะมีการ unlock เกิดขึ้น

การใช้งาน NSLock นั้น ก็เพียงแค่ประกาศ NSLock ขึ้นมา และเรียกใช้ lock หรือ unlock เท่านั้นเอง

จากตัวอย่าง code ข้างบน ก็แก้ไข ส่วนของ interface โดยการเพิ่มตัวแปร NSLock และในส่วนของ method ก็ทำการ เรียกใช้ lock และ unlock

@interface MyArray : NSObject
{
    NSLock *m_lock;
    NSMutableArray* m_array;
}

ส่วน Method ก็ทำการแก้ไขได้เป็น

- (void) AddMember: (NSString*) text
{
     [m_lock];
     for( int i = 0 ; i < 100 ; i++)
     {
        [m_array addObject:[[NSMutableString alloc]initWithString:text]];
     }
     [m_unlock];
}
 
- (void) AddMoreText: (NSString*) text
{
    [m_lock];
    for( int i = 0 ; i < [m_array count] ; i++)
    {
        [[m_array objectAtIndex:i] appendString:text];
    }
    [m_unlock];
}

เพียงเท่านี้ก็แก้ปัญหาได้แล้ว สำหรับผลลัพธ์ ที่่ออกมาก็คือ

0 Hello World!!
1 Hello World!!
.
.
98 Hello World!!
99 Hello World!!

จะเห็นว่า จะไม่เกิดการซ้อนทับ เหมือนกับ ตัวอย่างแรก

@synchronized

ยังมีอีกวิธี ที่สามารถทำการ lock ในส่วนที่ต้องการได้ ถ้าไม่ต้องการใช้ NSLock เราเพียงแค่เขียน code ที่ต้องการจะ lock ไว้ภายในส่วนของ @synchronized { } ก็ทำได้เหมือนกัน

เป็นต้นว่า

- (void) AddMoreText: (NSString*) text
{
    @synchronized(self)
    {
         for( int i = 0 ; i < [m_array count] ; i++)
         {
             [[m_array objectAtIndex:i] appendString:text];
         }
    }
}

ก็ติดปัญหาอะไร ถามได้น่ะครับ

Technorati Tags: , , , , ,


3 responses so far, want to say something?

  1. Avatar

    skyper_v says:

    ช่วยอธิบาย @synchronized(self) หน่อยครับ ยังงงๆ อยู่เลย
    ว่า @synchronized(self) จะ Lock ส่วนไหน
    เท่าที่ผมอ่านแล้วเข้าใจคือ
    @synchronized(self) {
    // จะ Lock code ที่อยู่ในนี้ใช่ไหมครับ
    }

    แล้วการประกาศ @synchronized มีแบบ อื่นอีกไหมครับ นอกจากส่ง self

    มีข้อมูลจากไหนเพิ่มเติมบ้างครับ
    ขอบคุณครับ

  2. Avatar

    admin says:

    ครับ synchronized(self) จะ lock แต่ในส่วนของ { } เท่านั้น อย่างอื่นไม่ได้ lock
    ส่วน synchronized(self) ก็ไม่จำเป็นต้องเป็น แบบนี้ครับ ตรง self จะเปลี่ยนเป็น object อื่นๆก็ได้ครับ

    ดูตัวอย่างจาก http://developer.apple.com/documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocThreading.html#//apple_ref/doc/uid/TP30001163-CH19-BCIIGGHG

    แต่จริงๆ แล้วเค้าไม่ค่อยจะใช้ synchronized ในกรณีที่ มี thread ที่ทำงานแตกต่างกัน แต่ต้องการเข้าใช้ resource อันเดียวกัน เค้าจะนิยมใช้ NSLock ซะมากกว่า

    ในการณีใช้ synchronized มักจะใช้กับ กรณี thread แต่ทำงานแบบเดียวกัน

  3. Avatar

    ladian says:

    ผมลองสร้าง thread มาโหลดอิมเมจแล้วลองวาดดูได้รูปเป็นสีขาวเพราะอะไรครับมีวิธีแก้ไหมครับ งงอยู่ๆ

Leave a Reply

You must be logged in to post a comment.