这篇文章主要讲解了“线程调度的随机性有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“线程调度的随机性有哪些”吧!
望都ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联建站的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:18982081108(备注:SSL证书合作)期待与您的合作!
线程调度的几个基本知识点
多线程并发执行时有很多同学捋不清楚调度的随机性会导致哪些问题,要知道如果访问临界资源不加锁会导致一些突发情况发生甚至死锁。
关于线程调度,需要深刻了解以下几个基础知识点:
调度的最小单位是轻量级进程【比如我们编写的hello world最简单的C程序,执行时就是一个轻量级进程】或者线程;
每个线程都会分配一个时间片,时间片到了就会执行下一个线程;
线程的调度有一定的随机性,无法确定什么时候会调度;
在同一个进程内,创建的所有线程除了线程内部创建的局部资源,进程创建的其他资源所有线程共享;比如:主线程和子线程都可以访问全局变量,打开的文件描述符等。
实例
再多的理论不如一个形象的例子来的直接。
预期代码时序
假定我们要实现一个多线程的实例,预期程序执行时序如下:
期待时序
期待的功能时序:
鸿蒙官方战略合作共建——HarmonyOS技术社区
主进程创建子线程,子线程函数function();
主线程count自加,并分别赋值给value1,value2;
时间片到了后切换到子线程,子线程判断value1、value2值是否相同,如果不同就打印信息value1,value2,count的值,但是因为主线程将count先后赋值给了value1,value2,所以value1,value2的值应该永远不相同,所以不应该打印任何内容;
重复2、3步骤。
代码1
好了,现在我们按照这个时序编写代码如下:
1 #include2 #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 #include2 #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 } 0 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;
时序图如下:
如上图所示:
时刻n,主线程获得mutex,从而进入临界区;
时刻n+1,时间片到了,切换到子线程;
n+2时刻子线程申请不到锁mutex,所以放弃cpu,进入休眠;
n+3时刻,主线程释放mutex,离开临界区,并唤醒阻塞在mutex的子线程,子线程申请到mutex,进入临界区;
n+4时刻,子线程离开临界区,释放mutex。
可以看到,加锁之后,即使主线程在value2 =count; 之前产生了调度,子线程由于获取不到mutex,会进入休眠,只有主线程出了临界区,子线程才能获得mutex,访问value1和value2,就永远不会打印信息,就实现了我们预期的代码时序。
感谢各位的阅读,以上就是“线程调度的随机性有哪些”的内容了,经过本文的学习后,相信大家对线程调度的随机性有哪些这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!