线程创建后,未必立即运行

2023-12-11 05:18

本文主要是介绍线程创建后,未必立即运行,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

    线程创建,并不立即执行,而是等时间片到来后再执行。

 

     一个进程,包含多个线程。

      则这个线程共享进程的数据等资源,各个线程呈现并发执行状态。

      线程执行时,所传入的参数值是执行时才传入的,而不是创建线程时传入的。

 

 

考虑以下程序:

 

#include "stdafx.h"
#include <iostream>
#include <Windows.h>
#include <process.h>
using namespace std;
long g_nNum;
unsigned int _stdcall ThreadFun(void *pPM);
const int THREAD_NUM=10;
CRITICAL_SECTION g_csThreadCore;
HANDLE hSemaphore;


 

int main(array<System::String ^> ^args)
{
g_nNum=0;
HANDLE handle[10];
int i=0;
//初始化关键段
InitializeCriticalSection(&g_csThreadCore);
//创建信号量
hSemaphore=CreateSemaphore(NULL,0,1,NULL);
while(i<THREAD_NUM)
{
handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,NULL);
//	WaitForSingleObject(hSemaphore,INFINITE);
i++;
}
WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);
DeleteCriticalSection(&g_csThreadCore);
CloseHandle(hSemaphore);
cin>>i;
return 0;
}
unsigned int _stdcall  ThreadFun(void *pPM)
{
int nThreadnum=*(int*)pPM;
ReleaseSemaphore(hSemaphore,1,NULL);
//Sleep(50);
EnterCriticalSection(&g_csThreadCore);
g_nNum++;
//	Sleep(50);
printf("线程编号为%d  全局资源值为%d\n",nThreadnum,g_nNum);
LeaveCriticalSection(&g_csThreadCore);
return 0;
}


 

  

运行结果:

 

线程编号为7  全局资源值为1
线程编号为7  全局资源值为2
线程编号为8  全局资源值为3
线程编号为7  全局资源值为4
线程编号为7  全局资源值为5
线程编号为7  全局资源值为6
线程编号为10  全局资源值为7
线程编号为10  全局资源值为8
线程编号为7  全局资源值为9
线程编号为7  全局资源值为10

由此可见,线程编号不正确,原因是:

        创建线程后,不立即执行, 当执行时,传入参数的值已经改变。

        而且,线程编号有重复值, 这说明在i值改变前,启动了好几个线程, 传入这些线程的值都是i(编号),因此才会出现线程编号重复问题。

        比如: i==0时,创建线程0

                      i==1时,创建线程1

                      i==2时,创建线程2

         但,此时还没有一个线程执行,当i=3时,创建线程3,

         此时,时间片轮到线程了,因此可能线程0开始执行,但执行线程0时,传入的为当前i的值3, 因此线程0的编号被误改为3,这个线程未执行完,又启动了其他线程,比如线程1,但线程1传入的参数也是i的当前值3

 

如何才能让线程编号正确?

之所以不正确显示,是因为线程执行时,未能传入正确的i值。

因此,如果线程创建成功执行时,能传入正确值的话,这个问题就解决了。

怎样传入正确值呢?

handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,NULL);

i++;

创建线程i,此线程创建后,并没有立即执行,而是直接i++ 然后创建另一个线程。

如果在i++之前,能确保此线程执行的话,则能传入正确的i值。。。。

 

  handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,NULL);
  WaitForSingleObject(hSemaphore,INFINITE);
  i++;

 

WaitForSingleObject(hSemaphore,INFINITE);

直到hSemaphore有信号时,才往下继续执行,而只有当创建的线程执行时,Semaphore才有信号。

因为线程函数中有 设置信号的函数: ReleaseSemaphore(hSemaphore,1,NULL);

 

这样就保证了线程编号的正确。

 

int main(array<System::String ^> ^args)
{
g_nNum=0;
HANDLE handle[10];
int i=0;
//初始化关键段
InitializeCriticalSection(&g_csThreadCore);
//创建信号量
hSemaphore=CreateSemaphore(NULL,0,1,NULL);
while(i<THREAD_NUM)
{
handle[i]=(HANDLE)_beginthreadex(NULL,0,ThreadFun,&i,0,NULL);
WaitForSingleObject(hSemaphore,INFINITE);
i++;
}
WaitForMultipleObjects(THREAD_NUM,handle,TRUE,INFINITE);
DeleteCriticalSection(&g_csThreadCore);
CloseHandle(hSemaphore);
cin>>i;
return 0;
}


线程编号为0  全局资源值为1
线程编号为1  全局资源值为2
线程编号为2  全局资源值为3
线程编号为3  全局资源值为4
线程编号为4  全局资源值为5
线程编号为5  全局资源值为6
线程编号为6  全局资源值为7
线程编号为7  全局资源值为8
线程编号为8  全局资源值为9
线程编号为9  全局资源值为10

注意:线程函数除设置信号量外,还设置了临界区,临界区至多只有一个线程访问。

当一个线程进入临界区后,其它线程只能等待它退出临界区后,再访问。

这保证了,临界区内的全局数据不被多个线程同时修改,保证了临界区内的程序顺序执行

 

	EnterCriticalSection(&g_csThreadCore);
g_nNum++;
printf("线程编号为%d  全局资源值为%d\n",nThreadnum,g_nNum);
LeaveCriticalSection(&g_csThreadCore);


假如: 去掉临界区

线程编号为3  全局资源值为4
线程编号为2  全局资源值为1
线程编号为0  全局资源值为2
线程编号为1  全局资源值为3
线程编号为8  全局资源值为5
线程编号为9  全局资源值为6
线程编号为5  全局资源值为8
线程编号为7  全局资源值为10
线程编号为6  全局资源值为7
线程编号为4  全局资源值为9

由此可见,全局资源顺序有误,甚至会有重复现象

这是因为:各个线程 同时访问全局变量,所导致的不一致

 

 

参考资料: http://blog.sina.com.cn/s/blog_6120646301012szj.html

这篇关于线程创建后,未必立即运行的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/479564

相关文章

idea中创建新类时自动添加注释的实现

《idea中创建新类时自动添加注释的实现》在每次使用idea创建一个新类时,过了一段时间发现看不懂这个类是用来干嘛的,为了解决这个问题,我们可以设置在创建一个新类时自动添加注释,帮助我们理解这个类的用... 目录前言:详细操作:步骤一:点击上方的 文件(File),点击&nbmyHIgsp;设置(Setti

Spring Boot3虚拟线程的使用步骤详解

《SpringBoot3虚拟线程的使用步骤详解》虚拟线程是Java19中引入的一个新特性,旨在通过简化线程管理来提升应用程序的并发性能,:本文主要介绍SpringBoot3虚拟线程的使用步骤,... 目录问题根源分析解决方案验证验证实验实验1:未启用keep-alive实验2:启用keep-alive扩展建

Java终止正在运行的线程的三种方法

《Java终止正在运行的线程的三种方法》停止一个线程意味着在任务处理完任务之前停掉正在做的操作,也就是放弃当前的操作,停止一个线程可以用Thread.stop()方法,但最好不要用它,本文给大家介绍了... 目录前言1. 停止不了的线程2. 判断线程是否停止状态3. 能停止的线程–异常法4. 在沉睡中停止5

Spring 中使用反射创建 Bean 实例的几种方式

《Spring中使用反射创建Bean实例的几种方式》文章介绍了在Spring框架中如何使用反射来创建Bean实例,包括使用Class.newInstance()、Constructor.newI... 目录1. 使用 Class.newInstance() (仅限无参构造函数):2. 使用 Construc

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

Java捕获ThreadPoolExecutor内部线程异常的四种方法

《Java捕获ThreadPoolExecutor内部线程异常的四种方法》这篇文章主要为大家详细介绍了Java捕获ThreadPoolExecutor内部线程异常的四种方法,文中的示例代码讲解详细,感... 目录方案 1方案 2方案 3方案 4结论方案 1使用 execute + try-catch 记录

在VSCode中本地运行DeepSeek的流程步骤

《在VSCode中本地运行DeepSeek的流程步骤》本文详细介绍了如何在本地VSCode中安装和配置Ollama和CodeGPT,以使用DeepSeek进行AI编码辅助,无需依赖云服务,需要的朋友可... 目录步骤 1:在 VSCode 中安装 Ollama 和 CodeGPT安装Ollama下载Olla

解读docker运行时-itd参数是什么意思

《解读docker运行时-itd参数是什么意思》在Docker中,-itd参数组合用于在后台运行一个交互式容器,同时保持标准输入和分配伪终端,这种方式适合需要在后台运行容器并保持交互能力的场景... 目录docker运行时-itd参数是什么意思1. -i(或 --interactive)2. -t(或 --

Python中conda虚拟环境创建及使用小结

《Python中conda虚拟环境创建及使用小结》本文主要介绍了Python中conda虚拟环境创建及使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录0.前言1.Miniconda安装2.conda本地基本操作3.创建conda虚拟环境4.激活c

Spring Boot 中正确地在异步线程中使用 HttpServletRequest的方法

《SpringBoot中正确地在异步线程中使用HttpServletRequest的方法》文章讨论了在SpringBoot中如何在异步线程中正确使用HttpServletRequest的问题,... 目录前言一、问题的来源:为什么异步线程中无法访问 HttpServletRequest?1. 请求上下文与线