竞态条件
同一个进程的线程共享进程内的绝大部分资源,当一段访问这些共享资源的代码块,有可能被多个线程执行时,那么这段代码块就称为临界区。 当有多个线程并发的在临界区执行时,程序的执行结果会出现不确定性,这种情况称之为竞态条件。
实例:
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<errno.h> #define handle_error_en(en, msg) do { errno = en; perror(msg); exit(EXIT_FAILURE); } while(0) //glob为多线程共享资源 static int glob = 0; //子线程访问修改共享资源glob static void *thread_routine(void *arg) { int loc, j; for (j = 0; j < 10000000; j++) { loc = glob; loc++; glob = loc; } return NULL; } int main() { pthread_t t1,t2; int s; //创建两个线程并发访问修改共享资源glob s = pthread_create(&t1, NULL, thread_routine, NULL); if (0 != s) { handle_error_en(s, "pthread_create"); } s = pthread_create(&t2, NULL, thread_routine, NULL); if (0 != s) { handle_error_en(s, "pthread_create"); } s = pthread_join(t1, NULL); if (0 != s) { handle_error_en(s, "pthread_join"); } s = pthread_join(t2, NULL); if (0 != s) { handle_error_en(s, "pthread_join"); } printf("glob = %d ", glob); exit(EXIT_SUCCESS); }
运行结果:
上述代码,每个线程都对glob进行了10000000次加1操作,glob的初值为0,因此理论上程序的执行结果应该是20000000。但是实际结果却没有达到预期。
出现以上结果的原因是因为多个线程并发的访问该临界区,从而出现了竞态条件。两个线程有可能以以下的时序执行,从而导致glob的最终值小于20000000。
互斥锁
多线程编程中,避免出现竞态条件的一项重要解决方案就是,保证多个线程在临界区是互斥的。所谓的互斥,就是指不能同时有多于一个线程进入临界区。
保证临界区互斥的重要技术,就是互斥锁。 互斥锁的初始化,有两种方式:静态初始化和动态初始化。
// 静态初始化一个全局的互斥锁 pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; #include <pthread.h> // 动态分配一个互斥锁 int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); // 释放动态分配的互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); #include <pthread.h> // 持有互斥锁 int pthread_mutex_lock(pthread_mutex_t *mutex); // 释放互斥锁 int pthread_mutex_unlock(pthread_mutex_t *mutex);
使用互斥锁,保证临界区互斥的一般思路是:
- 为该临界区分配一把互斥锁;
- 任何想要进入临界区的线程都必须先持有该互斥锁;
- 持有互斥锁运行于临界区的线程在离开临界区后必须释放该互斥锁;
- 假设某一临界区正在被一个线程A执行着,这意味着线程A持有该临界区的互斥锁M,如果此时有另一个线程B企图持有互斥锁M进入临界区,那么线程B将会进入阻塞状态。
使用互斥锁最常见的错误就是死锁,而所谓的死锁是指一个线程为了持有一把互斥锁而永远的阻塞了。 造成死锁原因主要有以下两种:
一个线程试图对其已经持有的互斥锁进行再次加锁;
当有需要持有多把锁时,线程间加锁的顺序不同时,也会造成死锁,如下图所示:
使用互斥锁修正glob的计算问题源代码如下:
#include<stdio.h> #include<pthread.h> #include<stdlib.h> #include<errno.h> #define handle_error_en(en, msg) do { errno = en; perror(msg); exit(EXIT_FAILURE); } while(0) //glob为多线程共享资源 static int glob = 0; static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER; //子线程访问修改共享资源glob static void *thread_routine(void *arg) { int loc, j; for (j = 0; j < 10000000; j++) { pthread_mutex_lock(&mtx); loc = glob; loc++; glob = loc; pthread_mutex_unlock(&mtx); } return NULL; } int main() { pthread_t t1,t2; int s; //创建两个线程并发访问修改共享资源glob s = pthread_create(&t1, NULL, thread_routine, NULL); if (0 != s) { handle_error_en(s, "pthread_create"); } s = pthread_create(&t2, NULL, thread_routine, NULL); if (0 != s) { handle_error_en(s, "pthread_create"); } s = pthread_join(t1, NULL); if (0 != s) { handle_error_en(s, "pthread_join"); } s = pthread_join(t2, NULL); if (0 != s) { handle_error_en(s, "pthread_join"); } printf("glob = %d ", glob); exit(EXIT_SUCCESS); }
运行结果: