王骏的博客
编程、网络技术点滴...

公告

逐渐将VC知识库的博客迁移到这里!

随笔分类

随笔档案

相册

最新评论

阅读排行榜

评论排行榜

程序员博客   首页  新随笔  订阅  管理  登录 
 
王骏的博客 阅读(2820) 评论(20)
问题由来: 一个多线程程序运行一段时间后变得不正常,许多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


评论列表
Diviner
re: string与线程安全
用你的代码,我用VC6+SP6测试无问题
wangjun
re: string与线程安全
多CPU系统下这个问题才会显现出来,运行时用release方式编译的版本效果会更明显。
周星星
re: string与线程安全
顶这一句 ---- “CPU具有真正的并发性造成……” !

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

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

模板库里面跟踪起来有点"晕"!
周星星
re: string与线程安全
在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与线程安全
如果将
string strTmp = GetString();
改为
LockString();
{
string strTmp = GetString();
}
UnlockString();
而不出错的话,也许可以证明我的观点是正确的。
wangjun
re: string与线程安全
星星的分析也很有道理,因为实际运行时发现出现异常的现象也是多样的。
wangjun
re: string与线程安全
我试了一下
LockString(); 

string strTmp = GetString(); 

UnlockString();

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

机器听音乐都听不下去了..唉。。
清风雨
关于线程同步
1.临界区的锁用于实现原子操作
2.过程操作都可能多线程访问

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

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

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

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

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

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

应该不是所谓的原子操作之类的
lixlin
re: string与线程安全

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

然后又同时多次执行程序,还是不出错,不知道为什么,
是不是对于多cpu环境下还需要特别的设置啊。
wangjun
re: string与线程安全
不知道VC5.0中得string是不是线程安全的,建议你的程序用VC7进行编译,这样放心一些。
liu
re: string与线程安全
将 strResult = g_str换成
strResult = g_str.c_str() 就不会出现问题,因为是深层COPY

发表评论
切换编辑模式