王骏的BLOG
编程、网络技术点滴...
<2008年9月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011
公告

留言簿(24)

随笔分类

随笔档案

文章分类

文章档案

相册

WEB开发

相关链接

搜索

最新评论

阅读排行榜

评论排行榜

 
VC知识库BLOG   首页  新随笔  联系  聚合  登录 
  随笔-61 文章-5 评论-246 Trackbacks-0
问题由来: 一个多线程程序运行一段时间后变得不正常,许多string类型变量的内容不正常。
因为程序在本机运行一直正常,而拿到一台服务器上运行有问题,怀疑服务器上是多CPU具有
真正的并发性造成某个未同步的变量操作异常。检查所有应该同步的代码,似乎都进行了正确的同步。
大致代码如下:

#include "stdafx.h"
#include 
<process.h>
#include 
<iostream>
#include 
<conio.h>
#include 
<string>

string                g_str;
CRITICAL_SECTION    g_cs;

void LockString() { EnterCriticalSection(&g_cs); };
void UnlockString() { LeaveCriticalSection(&g_cs); };

void SetString(char * szText)
{
    LockString();

    g_str 
= szText;

    UnlockString();
}


string GetString()
{
    
string strResult;

    LockString();

    strResult 
= g_str;

    UnlockString();

    
return strResult;
}


UINT ThreadProc(LPVOID lpParam)
{
    
// 为了使现象明显,这里进行了大量循环
    for(int i = 0; i < 200000; i++)
    
{
        
string strTmp = GetString();
    }


    
return 0;
}


#define        MAX_THREADS        200

int main(void)
{
    ::InitializeCriticalSection(
&g_cs);

    SetString(
"VC知识库");

    HANDLE hThreads[MAX_THREADS];

    UINT nThreadID;
    
int i;

    
// 开启线程
    for(i = 0; i < MAX_THREADS; i++)
        hThreads[i] 
= (HANDLE)_beginthreadex(NULL, 0, (unsigned (__stdcall *)(void *))ThreadProc, NULL, 0&nThreadID);

    
// 等待线程结束
    for(i = 0; i < MAX_THREADS; i++)
        ::WaitForSingleObject(hThreads[i], INFINITE);
    
    
// 输出结果
    cout << "string:" << GetString() << endl;

    ::DeleteCriticalSection(
&g_cs);

    getch();
    
return 0;
}


代码中唯一共用的变量g_str已经用临界区进行了同步,似乎没有问题了。但运行的时候却发现有时没有运行到cout时程序便异常退出,
或cout并没有输出正确的字符串。

经过调试最后发现问题是出在 string strTmp = GetString();
因为VC6自带的STL中的string采用cow方式,这种字符串的浅拷贝带来了多线程时的安全问题。而且这种错误隐藏得比较深,很难调试排错。

结论:如果要在多线程程序中进行string变量的传递,建议使用深拷贝的string类,听说stlport没问题(本人没有测试过),如果采用VC.NET, 其自带的string是安全的。

测试环境:双Intel Xeon CPU 2.8G, WIN2003, VC6, VC.NET

posted on 2005-07-01 22:19 王骏的BLOG 阅读(3976) 评论(21)  编辑 收藏
Comments
  • # re: string与线程安全
    Diviner
    Posted @ 2005-07-02 09:03
    用你的代码,我用VC6+SP6测试无问题
  • # re: string与线程安全
    wangjun
    Posted @ 2005-07-02 09:30
    多CPU系统下这个问题才会显现出来,运行时用release方式编译的版本效果会更明显。
  • # re: string与线程安全
    周星星
    Posted @ 2005-07-02 09:48
    顶这一句 ---- “CPU具有真正的并发性造成……” !

    其实当初反对COW的呼声就很大,似乎P.J.Plauger浮躁了一回,在新版PJ STL中取消string的COW方式也是必然的,而且M$做了公告,承认vc6.0中的pj stl有问题。不过我一直小心眼,总觉得这是一个阴谋:)

    从你这段代码上看,只可能造成内存泄漏,不应该有其他后果呀,能不能告诉我问提出在哪儿?
  • # re: string与线程安全
    freedk
    Posted @ 2005-07-02 10:13
    不错!!!!又学了一点东西。
  • # re: string与线程安全
    wangjun
    Posted @ 2005-07-02 10:28
    我没有进一步分析,估计是cow的实现里面需要一个引用计数器:Refcnt, 当Refcnt为0的时候进行delete, 很可能是多线程的时候Refcnt出现了混乱,不该delete的时候delete了。

    模板库里面跟踪起来有点"晕"!
  • # re: string与线程安全
    周星星
    Posted @ 2005-07-02 10:42
    在google上找了一下,说多线程下COW有问题的一大坨,但就实现指出问题所在的一个都没有。但我也猜想出一个大概来:
    在执行 strResult = g_str 时,可能另一个CPU上的线程在执行 strTmp 的析构,因为采用COW的缘故,所以 g_str._Ptr 和 strTmp._Ptr 可能(之所以只能说"可能",那是因为只用1个字节来存放计数值,当计数值大于253时,会拷贝一个新体)指向同一个地方,operator=和~basic_string同时对_Ptr[-1]进行存取,一个想将它+1,一个想将它-1,结果是多样的,可能啥事也没有,可能引起后来的内存泄漏(如果-1被覆盖了),也可能将一个字符串提前释放了(如果+1被覆盖了)。
  • # re: string与线程安全
    周星星
    Posted @ 2005-07-02 10:49
    如果将
    string strTmp = GetString();
    改为
    LockString();
    {
    string strTmp = GetString();
    }
    UnlockString();
    而不出错的话,也许可以证明我的观点是正确的。
  • # re: string与线程安全
    wangjun
    Posted @ 2005-07-02 10:57
    星星的分析也很有道理,因为实际运行时发现出现异常的现象也是多样的。
  • # re: string与线程安全
    wangjun
    Posted @ 2005-07-02 11:32
    我试了一下
    LockString(); 

    string strTmp = GetString(); 

    UnlockString();

    是没问题的。看来问题的关键就是在构造与析构的时候。
  • # re: string与线程安全
    Diviner
    Posted @ 2005-07-02 11:33
    学习。
  • # re: string与线程安全
    Diviner
    Posted @ 2005-07-02 11:36
    到现在为止,DELPHI用的还是COW技术,这项技术本来也有好多人提倡的,后来因为多线程和operator char[]的问题基本上都去掉了。
  • # re: string与线程安全
    freedk
    Posted @ 2005-07-02 13:23
    vc6.0+无sp,无任何输出:
        // 等待线程结束
        for(i = 0; i < MAX_THREADS; i++)
            ::WaitForSingleObject(hThreads[i], INFINITE);//等待时间N长.......

    机器听音乐都听不下去了..唉。。
  • # 关于线程同步
    清风雨
    Posted @ 2005-07-04 09:17
    1.临界区的锁用于实现原子操作
    2.过程操作都可能多线程访问

    所以:string的对象构造上需要原子保护,因此周的做法是正确的。
  • # 至于为什么2003自带的没问题
    清风雨
    Posted @ 2005-07-04 09:34
    比较懒惰,没去看它们各自的实现(看起来痛苦,一堆的宏)。

    多线程同时读,是不需要保护的(没有数据破坏)。可以推断,2003的没有共享资源的写操作。

    原来上面回帖就在讨论那个共享资源的写!

    那么对共享资源:
    1.显式,自己看的到的实现
    2.隐式,自己看不到的实现

    所以,自己向来都怕怕:在C++下不敢对函数体实现原子,而一般采用调用原子化。
  • # re: string与线程安全
    wangjun
    Posted @ 2005-07-04 10:56
    但 调用原子化 处理起来有时候不够顺手:(
  • # 是啊!谁要我们是C++程序呢
    清风雨
    Posted @ 2005-07-04 11:59
    也确实。有次企图去封装,结果死锁了。(最后也没能封装成功,后来放弃了)

    不过,直接写,有时还可以优化!^_^
  • # re: string与线程安全
    Diviner
    Posted @ 2005-07-04 12:50
    个人认为应该是汇编层的问题,很可能有些汇编代码在单CPU的机器上多线程运行了没问题,但在多线程机器上是有问题的。

    应该不是所谓的原子操作之类的
  • # re: string与线程安全
    lixlin
    Posted @ 2005-10-27 13:40

    你好,我这里好像也碰到了类似的问题(多cpu下string线程不安全),
    但是把你的source考过来,release版之后,放在多cpu下并不出错啊
    运行环境:2个AMD Opteron Processor 244 1.8 GHz
            Win2000 server
    编译环境:
            VC5.0
            runtime lib : multi thread

    然后又同时多次执行程序,还是不出错,不知道为什么,
    是不是对于多cpu环境下还需要特别的设置啊。
  • # re: string与线程安全
    wangjun
    Posted @ 2005-10-27 16:24
    不知道VC5.0中得string是不是线程安全的,建议你的程序用VC7进行编译,这样放心一些。
  • # re: string与线程安全
    liu
    Posted @ 2007-01-12 17:56
    将 strResult = g_str换成
    strResult = g_str.c_str() 就不会出现问题,因为是深层COPY
  • # re: string与线程安全
    http://www.axdat.com
    Posted @ 2008-09-03 10:47
    请做好数据备份,如遇数据丢失,请咨询安信数据恢复中心

     http://www.db-recovery.com   数据库恢复
     http://sh.db-recovery.com    上海数据恢复
     http://raid.db-recovery.com  raid数据恢复/磁盘阵列/服务器数据恢复上海
     http://www.fix.ac.cn         数据库修复                    
     http://www.axdat.com         硬盘数据恢复  
标题  
姓名  
主页
验证码 *
内容   
  登录  使用高级评论  Top
[使用Ctrl+Enter键可以直接提交]