Multithreaded programming. Socketsand asynchronous I/O
Multithreaded programming
• OS implements scheduler – determines which threads execute
• Scheduling may execute threads in arbitrary order
• Without proper synchronization, code can execute non-deterministically
• Suppose we have two threads: 1 reads a variable, 2 modifies that variable
• Scheduler may execute 1, then 2, or 2 then 1
• Non-determinism creates a race condition – where the behavior/result depends on the order of execution
线程是由操作系统来控制的,而不是程序自身。尽管进程也是由操作系统控制,在执行的时候会中断,但因为进程有自己的内存空间,因此一般不会发生线程中共享资源访问的问题。Racecondition是指结果受执行顺序影响的情况。
Race conditions in assembly
Consider the following program race.c:
unsigned int cnt =0;
void ∗count (void ∗arg){/∗thread body∗/
int i;
for ( i = 0 ; i < 100000000; i ++)
cnt++;
return NULL;
}
int main(void){
pthread_t tids[4];
int i;
for (i = 0; i < 4; i++)
pthread_create(&tids[i], NULL, count, NULL);
for(i = 0; i < 4; i++)
pthread_join(tids[i], NULL);
printf("cnt=%u\n", cnt);
return 0;
}
What is the value of cnt?
[Bryant and O’Halloran. Computer Systems: A Programmer’sPerspective. Prentice Hall, 2003.]
Ideally, should increment cnt 4 × 100000000 times, so cnt =400000000. However, running our code gives:
athena% ./race.o
cnt=137131900
athena% ./race.o
cnt=163688698
athena% ./race.o
cnt=169695163
So, what happened?
• C not designed for multithreading
• No notion of atomic operations in C
• Increment cnt++; maps to three assembly operations:
1. load cnt into a register
2. increment value in register
3. save new register value as new cnt
• So what happens if thread interrupted in the middle?
• Race condition!
上述程序结果令人吃惊,一个重要原因是我本以为++是原子操作,现在发现++并不是原子操作,看来要真正理解程序,还是要学编译原理啊。如何解决这个问题?很简单,加锁使其成为原子操作皆可。加锁看起来是一种简便的方式,随程序的复杂,锁也带来了很多问题,尤其是手动来控制加锁、解锁的过程,更容易出错。上面也提到,问题的一个重要根源就是C在设计时并没有考虑多线程的问题。而Java等变成语言提供了synchronized关键字等设施来自动地解决这个问题,大大减轻了程序员的负担。
Let’s fix our code:
pthread_mutex_t mutex;
unsigned int cnt = 0;
void ∗count (void ∗arg){/∗thread body∗/
int i;
for(i = 0; i < 100000000; i++){
pthread_mutex_lock(&mutex);
cnt++;
pthread_mutex_unlock (&mutex ) ;
}
return NULL;
}
int main ( void ) {
pthread_t tids[4];
int i;
pthread_mutex_init(&mutex, NULL);
for (i = 0; i < 4; i++)
pthread_create(&tids[i], NULL, count, NULL);
for(i = 0; i < 4; i++)
pthread_join(tids[i], NULL);
pthread_mutex_dest roy (&mutex ) ;
printf("cnt=%u\n", cnt);
return 0;
}
如上面的代码所示,对读写公共数据的操作加锁,即可解决这个问题。当然,加解锁自身必须是原子操作,否则加解锁的过程也是racecondition。
Race conditions
• Note that new code func