_beginthreadex,CreateThread和_beginthread

转:https://blog.youkuaiyun.com/foreveyking/article/details/78029328

由于历史原因,所以C/C++运行库并不是为多线程应用程序而设计的,所以为了保证其中的某些变量和函数的安全,那么必须创建一个数据结构,并使之与使用了C/C++运行库函数的每个线程所关联。当在调用C/C++运行库函数时,那些函数必须读取主调自己的线程的数据块,从而避免印象其他线程。

       所以当编写C/C++代码时,请调用_beginthreadex,而不要使用CreateThread的大致原因就是如上所述。详细内容请继续往下读大笑

       _beginthreadex的函数参数列表与CreateThread一样,但是参数名和类型有所不同。这是因为C/C++程序不应该对Windows系统的类型有任何的依赖。

       _beginthreadex也会返回新建线程的句柄,就像CreateThread一样。可以非常方便的替换自己程序中的CreateThread函数,但是数据类型不一样,所以还得添加一些类型转换即可。

       透过_beginthreadex函数的源码我们可知,

        1.每个线程都有自己专用的_tiddata内存块,它们是从C/C++运行库的堆上分配的。

        2.传给_beginthreadex的线程函数地址(pfnStartAddr)保存在_tiddata内存块中。(_tiddata结构可以在Mtdll.h源码中窥探一二)

        3._beginthreadex会在内部调用CreateThread。

       4.调用CreateThread时传入的地址是_threadstartex(而非传入的pfnStartAddr)。参数地址是_tiddata结构地址,而非pvParam。

        5.如果一切顺利,会返回线程的句柄,操作失败会返回0。

 

      初始化并且传入_tiddata后,又怎么将其和线程关联呢?继续往下看大笑

      对于_threadstartex函数及其辅助函数__callthreadstartex:

        1.新的线程首先执行RtlUserThreadStart,然后在跳转到_threadstartex。(这便于上一篇所讲的"创建线程的内幕"一文中的知识点所契合)

        2._threadstartex唯一的参数就是新线程的_tiddata内存块地址。

      3.TlsSetValue是一个操作系统函数,作用是将一个值与主调函数关联起来。(这就是所谓的线程局部存储TLS)_threadstartex函数将_tiddata内存块与新建线程关联起来。------华丽的分割线--------

        4.无参数的辅助函数__callthreadstartex中,有一个SEH帧,它处理着许多与运行库有关的事情——比如运行时错误(如抛出未被捕获的C++异常)——和c/c++运行库的signal函数。这点很重要,因为用CreateThread创建的线程,调用C/C++运行库的signal函数,其是不能正常工作的。

       5.预期要执行的线程函数会被调用,并向其传递预期的参数。(线程的地址和参数都是存在于TLS中保存的_tiddata数据块中,并会在__callthreadstartex中在TLS中获取得到)6.线程函数的返回值被认为是线程的退出代码。注意:函数不是返回_threadstartex然后返回RltUserThreadStart(因为这样_tiddata内存块不会被释放),而是调用_endthreadex,并向其传入线程函数的返回值。

对于_endthreadex,其内部先获取TLS中的_tiddata块,然后释放掉,并调用操作系统的ExitThread函数来实际销毁线程,并传递正确的退出码。

 

        总结:所以现在应该理解为什么不要调用CreateThread了,因为调用CreateThread后,在线程调用一个需要_tiddata块的c/c++运行库函数时(主要通过TlsGetValue),会得到NULL,这时c/c++运行库会为其初始化一个块,然后这个块与线程关联(主要通过TlsSetValue)。这之后,任何使用_tiddata块的函数都能正常使用了。但是,问题还是有的:1.如果线程使用了signal函数,则整个进程会终止掉(上文有详细描述,关于__callthreadstartex的SEH帧没有准备就绪)。2.线程入口点函数返回后,会在RtlUserThreadStart中直接调用ExitThread,其并没有释放掉_tiddata内存块,这会引起内存泄漏。

 

 

        附加:绝不调用_beginthread。因为,1._beginthread参数较少,不能创建具有安全属性的线程,不能让线程立即挂起等。_endthread也是如此,其退出代码被硬编码为0。

_endthread还存在一个问题,就是在调用ExitThread前,会调用CloseHandle,向其传入新线程的句柄。举个例子,比如你使用了_beginthread创建了一个新的线程,在其之后使用CloseHandle关闭线程句柄。由于_beginthread会调用_endthread函数终止线程,而线程内核对象的初始化计数为2,在_endthread函数中,调用ExitThread前,会调用CloseHandle,也就是说会连续两次递减引用计数,这时引用计数为0,内核对象被系统销毁,再之后在调用CloseHandle关闭线程句柄时,这个传入的句柄是无效的,即CloseHandle函数的调用会以失败告终。所以请用_beginthreadex代替_beginthread,_endthreadex代替_endthread。      _beginthreadex调用的是_endthreadex,_beginthread调用的是_endthread

<think>好的,用户遇到了Git合并时本地更改会被覆盖的问题,需要解决方案。根据之前的引用资料,解决方法主要有commit、stashrevert三种。首先,我得确认用户的具体情况,比如他们的本地更改是否已经完成,是否需要保留,或者是否可以丢弃。用户可能不太清楚这三种方法的适用场景,需要详细解释每个选项的步骤优缺点。比如,commit适用于需要保留更改的情况,stash适合临时保存以便后续恢复,而revert则是直接撤销更改,适用于不需要保留的情况。需要结构清晰,分点说明,每个方法都给出操作步骤适用场景,帮助用户理解哪种情况最适合他们。还要注意使用中文,避免技术术语过于复杂,让用户容易理解。同时,生成相关问题,可能涉及如何恢复stash的内容,或者如何避免频繁冲突等,扩展用户的知识点。另外,要确保LaTeX格式正确,但在这个问题中可能用不上数学公式,所以主要关注代码块引用标识。检查引用是否正确,比如引用[^1][^2]的来源是否相关,用户提供的引用内容是否准确对应解决方案。 最后,确保回答真实可靠,操作步骤准确无误,避免误导用户。可能还需要提醒用户根据具体情况选择方法,比如如果有未完成的实验性代码,stash可能是更好的选择,而如果是确定要保留的修改,则应该commit。</think>要解决Git合并时本地更改被覆盖的问题,可根据不同场景选择以下三种方法: ### 一、提交本地更改(commit) **适用场景**:当本地更改已测试通过且需要保留到版本历史中。 **操作步骤**: 1. 将改动添加到暂存区: ```bash git add . # 添加所有文件 # 或指定文件 git add file1.txt file2.py ``` 2. 提交更改并添加描述: ```bash git commit -m "完成用户登录模块优化" ``` 3. 执行合并操作: ```bash git merge origin/main ``` **优点**:保留完整的开发记录,便于后续追溯[^1]。 ### 二、暂存更改(stash) **适用场景**:需要临时保存未完成的修改(如调试代码),优先处理合并操作。 **操作步骤**: 1. 保存当前工作状态: ```bash git stash # 可选添加备注 git stash save "用户登录模块调试代码" ``` 2. 查看暂存记录列表: ```bash git stash list ``` 3. 合并远程分支: ```bash git merge origin/main ``` 4. 恢复暂存内容: ```bash git stash pop # 恢复并删除最近一次暂存 # 或指定特定暂存 git stash apply stash@{1} ``` **优点**:保持工作目录整洁,特别适合处理紧急合并任务[^2]。 ### 三、撤销本地更改(revert) **适用场景**:本地修改可丢弃(如实验性代码或错误修改)。 **操作步骤**: 1. 撤销所有未提交的修改: ```bash git checkout -- . # 全部文件 # 或指定文件 git checkout -- file1.txt ``` 2. 执行合并操作: ```bash git merge origin/main ``` **风险提示**:此操作不可逆,需确认文件无需保留。 --- ### 对比决策表 | 方法 | 保留记录 | 可恢复性 | 适用阶段 | |---------|----------|----------|------------------| | commit | ✅ | ✅ | 功能开发完成时 | | stash | ❌ | ✅ | 开发中途临时切换 | | revert | ❌ | ❌ | 放弃无效修改 | ### 扩展应用场景 - 当存在多个暂存时,可用`git stash list`查看记录,通过`git stash apply stash@{n}`精准恢复 - 合并后出现冲突时,可使用`git mergetool`可视化工具解决冲突 - 长期分支开发推荐使用`git rebase`保持提交历史线性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值