当前位置:首页 > 公众号精选 > 小麦大叔
[导读]多线程并发执行时有很多同学捋不清楚调度的随机性会导致哪些问题,要知道如果访问临界资源不加锁会导致一些突发情况发生甚至死锁。

线程调度的几个基本知识点

多线程并发执行时有很多同学捋不清楚调度的随机性会导致哪些问题,要知道如果访问临界资源不加锁会导致一些突发情况发生甚至死锁。

关于线程调度,需要深刻了解以下几个基础知识点:

  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 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 
  7 unsigned int value1,value2, count=0;
  8 void *function(void *arg);
  9 int main(int argc,  char *argv[])
 10 
{
 11     pthread_t  a_thread;
 12 
 13     if (pthread_create(&a_thread, NULL, function, NULL) < 0)
 14     {
 15         perror("fail to pthread_create");
 16         exit(-1);
 17     }
 18     while ( 1 )
 19     {
 20         count++;
 21         value1 = count;
 22         value2 = count;
 23     }
 24     return 0;
 25 }
 26 
 27 void  *function(void *arg)
 28 
{
 29     while ( 1 )
 30     {
 31         if (value1 != value2)
 32         {                                                                                                                                                                                         
 33             printf("count=%d , value1=%d, value2=%d\n",  count, value1, value2);
 34             usleep(100000);
 35         }     
 36     }
 37     return  NULL;
 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_t  mutex;
pthread_mutex_init(&mutex, NULL)

申请释放锁:

pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);

原理:进入临界区之前先申请锁,如果能获得锁就继续往下执行, 如果申请不到,就休眠,直到其他线程释放该锁为止。

代码2

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #define _LOCK_
  7 unsigned int value1,value2, count=0;
  8 pthread_mutex_t  mutex;
  9 void *function(void *arg);
 10 
 11 int main(int argc,  char *argv[])
 12 
{
 13     pthread_t  a_thread;
 14          
 15     if (pthread_mutex_init(&mutex, NULL) < 0)                                                                                                                                                          
 16     {
 17         perror("fail to mutex_init");
 18         exit(-1);
 19     }
 20 
 21     if (pthread_create(&a_thread, NULL, function, NULL) < 0)
 22     {
 23         perror("fail to pthread_create");
 24         exit(-1);
 25     }
 26     while ( 1 )
 27     {
 28         count++;
 29 #ifdef  _LOCK_
 30         pthread_mutex_lock(&mutex);
 31 #endif
 32         value1 = count;
 33         value2 = count;
 34 #ifdef  _LOCK_
 35         pthread_mutex_unlock(&mutex);
 36 #endif
 37     }
 38     return 0;
 39  }
40 
 41 void  *function(void *arg)
 42 
{
 43      while ( 1 )
 44      {
 45 #ifdef _LOCK_
 46         pthread_mutex_lock(&mutex);
 47 #endif           
 48 
 49         if (value1 != value2)  
 50         {
 51             printf("count=%d , value1=%d, value2=%d\n",  count, value1, value2);
 52             usleep(100000);
 53         }     
 54 #ifdef _LOCK_
 55         pthread_mutex_unlock(&mutex);
 56 #endif
 57      }
 58      return  NULL;
 59  }     

如上述代码所示:主线程和子线程要访问临界资源value1,value2时,都必须先申请锁,获得锁之后才可以访问临界资源,访问完毕再释放互斥锁。该代码执行之后就不会打印任何信息。我们来看下,如果程序在下述代码之间产生调度时,程序的时序图。

value1 = count; 
value2 = count;

时序图如下:如上图所示:

  1. 时刻n,主线程获得mutex,从而进入临界区;
  2. 时刻n+1,时间片到了,切换到子线程;
  3. n+2时刻子线程申请不到锁mutex,所以放弃cpu,进入休眠;
  4. n+3时刻,主线程释放mutex,离开临界区,并唤醒阻塞在mutex的子线程,子线程申请到mutex,进入临界区;
  5. n+4时刻,子线程离开临界区,释放mutex。

可以看到,加锁之后,即使主线程在value2 =count; 之前产生了调度,子线程由于获取不到mutex,会进入休眠,只有主线程出了临界区,子线程才能获得mutex,访问value1和value2,就永远不会打印信息,就实现了我们预期的代码时序。

总结

实际项目中,可能程序的并发的情况可能会更加复杂,比如多个cpu上运行的任务之间,cpu运行的任务和中断之间,中断和中断之间,都有可能并发。

有些调度的概率虽然很小,但是不代表不发生,而且由于资源同步互斥导致的问题,很难复现,纵观Linux内核代码,所有的临界资源都会对应锁。

多阅读Linux内核源码,学向大神学习,与大神神交。

正所谓代码读百遍,其义自见!熟读代码千万行,不会编写也会抄!

关于内核和应用程序的同步互斥的知识点,可以查看一口君的其他文章。


—— The End —

推荐好文   点击蓝色字体即可跳转
 感觉身体被掏空!只因为肝了这篇空间矢量控制算法
 当心!别再被大小端的问题坑了
 PID微分器与滤波器的爱恨情仇
 简易PID算法的快速扫盲
 增量式PID到底是什么?
 三面大疆惨败,因为不懂PID的积分抗饱和

原创不易,欢迎转发、留言、点赞、分享给你的朋友,感谢您的支持!


长按识别二维码关注获取更多内容


免责声明:本文内容由21ic获得授权后发布,版权归原作者所有,本平台仅提供信息存储服务。文章仅代表作者个人观点,不代表本平台立场,如有问题,请联系我们,谢谢!

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭