线程存在的问题和临界区

前面我们知道了怎么创建线程,但我们都是只创建了一个线程,下面我们再来看看这样一个实例,创建100个线程,它们都访问了同一变量,其中一半对这个变量进行加1操作,一半进行减1操作,按道理其结果会等于0.

  1. #include 
  2.  
  3. #include 
  4.  
  5. #include 
  6.  
  7. #include 
  8.  
  9. #define NUM_THREAD 100 
  10.  
  11. void * thread_inc(void * arg); 
  12.  
  13. void * thread_des(void * arg); 
  14.  
  15. long long num = 0; //long long类型是64位整数型,多线程共同访问 
  16.  
  17. int main(int argc, char *argv[]) 
  18.  
  19.  
  20. pthread_t thread_id[NUM_THREAD]; 
  21.  
  22. int i; 
  23.  
  24. //创建100个线程,一半执行thread_inc,一半执行thread_des 
  25.  
  26. for(i = 0; i < NUM_THREAD; i++) 
  27.  
  28.  
  29. if(i %2) 
  30.  
  31. pthread_create(&(thread_id[i]), NULL, thread_inc, NULL); 
  32.  
  33. else 
  34.  
  35. pthread_create(&(thread_id[i]), NULL, thread_des, NULL); 
  36.  
  37.  
  38. //等待线程返回 
  39.  
  40. for (i = 0; i < NUM_THREAD; i++) 
  41.  
  42. pthread_join(thread_id[i], NULL); 
  43.  
  44. printf("result: %lld \n", num); //+1,-1按道理结果是0 
  45.  
  46. return 0; 
  47.  
  48.  
  49. //线程入口函数1 
  50.  
  51. void * thread_inc(void * arg) 
  52.  
  53.  
  54. for (int i = 0; i < 50000000; i++) 
  55.  
  56. num += 1;//临界区(引起问题的语句就是临界区位置) 
  57.  
  58. return NULL; 
  59.  
  60.  
  61. //线程入口函数2 
  62.  
  63. void * thread_des(void * arg) 
  64.  
  65.  
  66. for (int i = 0; i < 50000000; i++) 
  67.  
  68. num -= 1;//临界区 
  69.  
  70. return NULL; 
  71.  

TCP/IP网络编程 多线程服务器端的实现

从运行结果看并不是0,而且每次运行的结果都不同。那这是什么原因引起的呢? 是因为每个线程访问一个变量是这样一个过程:先从内存取出这个变量值到CPU,然后CPU计算得到改变后的值,最后再将这个改变后的值写回内存。因此,我们可以很容易看出,多个线程访问同一变量,如果某个线程还只刚从内存取出数据,还没来得及写回内存,这时其它线程又访问了这个变量,所以这个值就会不正确了。

接下来我们再来讲讲怎么解决这个问题:线程同步

线程同步

线程同步用于解决线程访问顺序引发的问题,一般是如下两种情况:

同时访问同一内存空间时发生的情况

需要指定访问同一内存空间的线程执行顺序的情况

针对这两种可能引发的情况,我们分别使用的同步技术是:互斥量和信号量。

互斥量

互斥量技术从字面也可以理解,就是临界区有线程访问,其它线程就得排队等待,它们的访问是互斥的,实现方式就是给临界区加锁与释放锁。

  1. #include 
  2.  
  3. int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); //创建互斥量 
  4.  
  5. int pthread_mutex_destroy(pthread_mutex_t *mutex);//销毁互斥量 
  6.  
  7. int pthread_mutex_lock(pthread_mutex_t *mutex);//加锁 
  8.  
  9. int pthread_mutex_unlock(pthread_mutex_t *mutex);//释放锁

简言之,就是利用lock和unlock函数围住临界区的两端。当某个线程调用pthread_mutex_lock进入临界区后,如果没有调用pthread_mutex_unlock释放锁退出,那么其它线程就会一直阻塞在临界区之外,我们把这种情况称之为死锁。所以临界区围住一定要lock和unlock一一对应。

实例代码:

  1. #include 
  2.  
  3. #include 
  4.  
  5. #include 
  6.  
  7. #include 
  8.  
  9. #define NUM_THREAD 100 
  10.  
  11. void * thread_inc(void * arg); 
  12.  
  13. void * thread_des(void * arg); 
  14.  
  15. long long num = 0; 
  16.  
  17. pthread_mutex_t mutex; 
  18.  
  19. int main(int argc, char *argv[]) 
  20.  
  21.  
  22. pthread_t thread_id[NUM_THREAD]; 
  23.  
  24. int i; 
  25.  
  26. //互斥量的创建 
  27.  
  28. pthread_mutex_init(&mutex, NULL); 
  29.  
  30. for(i = 0; i < NUM_THREAD; i++) 
  31.  
  32.  
  33. if(i %2) 
  34.  
  35. pthread_create(&(thread_id[i]), NULL, thread_inc, NULL); 
  36.  
  37. else 
  38.  
  39. pthread_create(&(thread_id[i]), NULL, thread_des, NULL); 
  40.  
  41.  
  42. for (i = 0; i < NUM_THREAD; i++) 
  43.  
  44. pthread_join(thread_id[i], NULL); 
  45.  
  46. printf("result: %lld \n", num); 
  47.  
  48. pthread_mutex_destroy(&mutex); //互斥量的销毁 
  49.  
  50. return 0; 
  51.  
  52.  
  53. /*扩展临界区,减少加锁,释放锁调用次数,但这样变量必须加满到50000000次后其它线程才能访问. 
  54.  
  55. 这样是延长了线程的等待时间,但缩短了加锁,释放锁函数调用的时间,这里没有定论,自己酌情考虑*/ 
  56.  
  57. void * thread_inc(void * arg) 
  58.  
  59.  
  60. pthread_mutex_lock(&mutex); //互斥量锁住 
  61.  
  62. for (int i = 0; i < 1000000; i++) 
  63.  
  64. num += 1; 
  65.  
  66. pthread_mutex_unlock(&mutex); //互斥量释放锁 
  67.  
  68. return NULL; 
  69.  
  70.  
  71. /*缩短了线程等待时间,但循环创建,释放锁函数调用时间增加*/ 
  72.  
  73. void * thread_des(void * arg) 
  74.  
  75.  
  76. for (int i = 0; i < 1000000; i++) 
  77.  
  78.  
  79. pthread_mutex_lock(&mutex); 
  80.  
  81. num -= 1; 
  82.  
  83. pthread_mutex_unlock(&mutex); 
  84.  
  85.  
  86. return NULL; 
  87.  




相关内容