一个例子让你看清线程调度的随机性

线程调度的几个基本知识点多线程并发执行时有很多同学捋不清楚调度的随机性会导致哪些问题 , 要知道如果访问临界资源不加锁会导致一些突发情况发生甚至死锁 。
关于线程调度 , 需要深刻了解以下几个基础知识点:

  1. 调度的最小单位是轻量级进程【比如我们编写的hello world最简单的C程序 , 执行时就是一个轻量级进程】或者线程;
  2. 每个线程都会分配一个时间片 , 时间片到了就会执行下一个线程;
  3. 线程的调度有一定的随机性 , 无法确定什么时候会调度;
  4. 在同一个进程内 , 创建的所有线程除了线程内部创建的局部资源 , 进程创建的其他资源所有线程共享; 比如:主线程和子线程都可以访问全局变量 , 打开的文件描述符等 。
实例再多的理论不如一个形象的例子来的直接 。
预期代码时序假定我们要实现一个多线程的实例 , 预期程序执行时序如下:
一个例子让你看清线程调度的随机性文章插图
期待时序
期待的功能时序:
  1. 主进程创建子线程 , 子线程函数function();
  2. 主线程count自加 , 并分别赋值给value1,value2;
  3. 时间片到了后切换到子线程 , 子线程判断value1、value2值是否相同 , 如果不同就打印信息value1,value2,count的值 , 但是因为主线程将count先后赋值给了value1,value2 , 所以value1,value2的值应该永远相同 , 所以不应该打印任何内容;
  4. 重复2、3步骤 。
代码1好了 , 现在我们按照这个时序编写代码如下:
1 #include2 #include3 #include4 #include5 #include 67 unsigned int value1,value2, count=0;8 void *function(void *arg);9 int main(int argc,char *argv[]) 10 { 11pthread_ta_thread; 1213if (pthread_create( 16exit(-1); 17} 18while ( 1 ) 19{ 20count++; 21value1 = count; 22value2 = count; 23} 24return 0; 25 } 2627 void*function(void *arg) 28 { 29while ( 1 ) 30{ 31if (value1 != value2) 32{33printf("count=%d , value1=%d, value2=%d\n",count, value1, value2); 34usleep(100000); 35}36} 37returnNULL; 38 }乍一看 , 该程序应该可以满足我们的需要 , 并且程序运行的时候不应该打印任何内容 , 但是实际运行结果出乎我们意料 。
编译运行:
gcc test.c -o run -lpthread./run代码1执行结果执行结果:
一个例子让你看清线程调度的随机性文章插图
可以看到子程序会随机打印一些信息 , 为什么还有这个执行结果呢? 其实原因很简单 , 就是我们文章开头所说的 , 线程调度具有?随机性 , 我们无法规定让内核何时调度某个线程 。有打印信息 , 那么这说明此时value1和value2的值是不同的 , 那也说明了调度子线程的时候 , 是在主线程向value1和value2之间的位置调度的 。
代码1执行的实际时序实际上代码的执行时序如下所示:
一个例子让你看清线程调度的随机性文章插图
如上图 , 在某一时刻 , 当程序走到**value2 = count;**这个位置的时候 , 内核对线程进行了调度 , 于是子进程在判断value1和value2的值的时候 , 发现这两个变量值不相同 , 就有了打印信息 。
该程序在下面这两行代码之间调度的几率还是很大的 。
value1 = count; value2 = count;解决方法如何来解决并发导致的程序没有按预期执行的问题呢? 对于线程来说 , 常用的方法有posix信号量、互斥锁 , 条件变量等 , 下面我们以互斥锁为例 , 讲解如何避免代码1的问题的出现 。
互斥锁的定义和初始化:
pthread_mutex_tmutex;pthread_mutex_init(pthread_mutex_unlock(原理: 进入临界区之前先申请锁 , 如果能获得锁就继续往下执行 ,如果申请不到 , 就休眠 , 直到其他线程释放该锁为止 。
代码21 #include2 #include3 #include4 #include5 #include 6 #define _LOCK_7 unsigned int value1,value2, count=0;8 pthread_mutex_tmutex;9 void *function(void *arg); 1011 int main(int argc,char *argv[]) 12 { 13pthread_ta_thread; 1415if (pthread_mutex_init( 18exit(-1); 19} 2021if (pthread_create( 24exit(-1); 25} 26while ( 1 ) 27{ 28count++; 29 #ifdef_LOCK_ 30pthread_mutex_lock( 31 #endif 32value1 = count; 33value2 = count; 34 #ifdef_LOCK_ 35pthread_mutex_unlock( 36 #endif 37} 38return 0; 39}4041 void*function(void *arg) 42 { 43while ( 1 ) 44{ 45 #ifdef _LOCK_ 46pthread_mutex_lock( 47 #endif4849if (value1 != value2)50{ 51printf("count=%d , value1=%d, value2=%d\n",count, value1, value2); 52usleep(100000); 53}54 #ifdef _LOCK_ 55pthread_mutex_unlock( 56 #endif 57} 58returnNULL; 59}如上述代码所示:主线程和子线程要访问临界资源value1,value2时 , 都必须先申请锁 , 获得锁之后才可以访问临界资源 , 访问完毕再释放互斥锁 。该代码执行之后就不会打印任何信息 。我们来看下 , 如果程序在下述代码之间产生调度时 , 程序的时序图 。