线程同步使用锁出现的奇怪问题

hdqqq 2005-12-16 11:30:38
mfc线程同步的时候,可以用CMutex或CCriticalSection,并用CSingleLock来进行锁定
象下面的代码

//CMutex gbmutex;
CCriticalSection gbmutex;

UINT thread1(LPVOID lparam)
{
int i;
CSingleLock lock(&gbmutex);
lock.Lock();
for (i = 0; i < 4; i++) {
cout << "1 thread output " << i << endl;
Sleep(1000);
}
lock.Unlock();
return 0;
}



UINT thread2(LPVOID lparam)
{
int i;
CSingleLock lock(&gbmutex);
lock.Lock();
for ( i = 0; i < 4; i++) {
cout << "2 thread output" << endl;
}
lock.Unlock();
if (lparam) {
CEvent* pevent = (CEvent*)lparam;
pevent->SetEvent();
}
return 0;
}

void thread_test()
{
CEvent le;
AfxBeginThread(thread1,NULL);
Sleep(1000);
le.ResetEvent();
AfxBeginThread(thread2,(void*)&le);
CSingleLock lock(&le);
lock.Lock();
}
thread_test中刻意让thread1先启动,并等了1秒
上面的代码在标准的控制台输出的时候如下:

1 thread output 0
1 thread output 1
1 thread output 2
1 thread output 3
2 thread output
2 thread output
2 thread output
2 thread output

但是,觉得这样使用数据同步的方式比较麻烦,所以,自己写了一个数据同步的适配器
代码如下:

template <class _data, class _mutex, class _lock>
class Mutex_Access_Adapter : public _data
{
private:
_mutex m_Mutex;
public:
BOOL Lock() {
_lock lock(&m_Mutex);
return lock.Lock();
}
BOOL Unlock() {
_lock lock(&m_Mutex);
return lock.Unlock();
}

public:
Mutex_Access_Adapter() {}
~Mutex_Access_Adapter() {}

};

上面的适配器自带了锁对象和同步的函数,所以,对于需要在线程之间需要同步操作的类
对象,可以这样使用.

struct __data_struct
{
};

typedef Mutex_Access_Adapter<__data_struct, CMutex, CSingleLock> threadsafe_data;

上面的threadsafe_data就自己带了同步的方式了.
然后,我写了下面的测试代码


threadsafe_data gbdata;

UINT thread3(LPVOID lparam)
{
int i;
gbdata.Lock();
for (i = 0; i < 4; i++) {
cout << "3 thread output " << i << endl;
Sleep(1000);
}
gbdata.Unlock();
return 0;
}



UINT thread4(LPVOID lparam)
{
int i;

gbdata.Lock();
for ( i = 0; i < 4; i++) {
cout << "4 thread output" << endl;
}
gbdata.Unlock();
if (lparam) {
CEvent* pevent = (CEvent*)lparam;
pevent->SetEvent();
}
return 0;
}

void other_thread_test()
{
CEvent le;
AfxBeginThread(thread3,NULL);
Sleep(1000);
le.ResetEvent();
AfxBeginThread(thread4,(void*)&le);
CSingleLock lock(&le);
lock.Lock();
}
上面的代码中thread3和thread4和thread1和thread2差不多,只是使用
了适配器修饰后的类的锁函数,但是输出结果如下:

3 thread output 0
3 thread out4 thread output
4 thread outpuput 1
t
4 thread output
4 thread output
这个没有象预期的那样,居然没有锁住.
奇怪之余,我看了mfc中CSingleLock中的实现代码,自己写了一个类似接口的
锁类.

//自定义的线程锁
template <typename _locktype>
class thread_lock
{
private:
_locktype* m_pLocktype;
public:
BOOL Lock() {
return m_pLocktype->Lock();
}
BOOL Unlock() {
return m_pLocktype->Unlock();
}

public:
thread_lock(_locktype* value) : m_pLocktype(value) {}
~thread_lock(){}
};

然后把上面的那个typedef 改写为
typedef Mutex_Access_Adapter<__data_struct, CMutex, thread_lock<CMutex> > threadsafe_data;
然后再运行上面的代码结果如下:
3 thread output 0
3 thread output 1
3 thread output 2
3 thread output 3
4 thread output
4 thread output
4 thread output
4 thread output
居然就锁住了,肯定是CSingleLock的区别,我自己写的锁没有CSingleLock那么复杂,但是我看CSingleLock的代码,它的构造函数
也很简单,很奇怪.

现在的问题是:
1. 如果一个对象的指针作为参数传入某个线程,如果线程通过这个指针调用对象的成员函数,那个成员函数中的 局部变量
是不是在线程的运行栈中构造? 我认为是的,不知道对不对.

2.为什么相同的使用方式,CSingleLock没锁住,自写的却锁住了,是否Mutex句柄有属于那个线程的区别,CSingleLock是否
可以递归锁定,说不清楚原因究竟在那里,所以希望大家能讨论一下.

上面的代码是在xp vc6 sp6 下测试的,可以建立一个支持mfc 的控制台程序,复制上面的代码测试,vc7下面我没有测试,欢迎大家来参与讨论.



...全文
367 点赞 收藏 6
写回复
6 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
hdqqq 2005-12-16
UINT thread3(LPVOID lparam)
{
int i;
gbdata.Lock();
for (i = 0; i < 4; i++) {
cout << "3 thread output " << i << endl;
Sleep(1000);
}
gbdata.Unlock();
return 0;
}
上面的线程中调用了gbdata.Lock(),是gbdata的成员函数,Lock函数的代码中会定义一个局部变量 _lock lock(&m_Mutex);
这个变量将在调用Lock函数的时候被生成,但是因为是在thread3中调用的,所以,我认为这个_lock lock(&m_Mutex); 应该存在
thread3的线程的运行栈中.

尽管线程没有自己的资源,我想每个线程在运行的时候应该有自己的栈的,因为AfxbeginThread中就有一个参数指定新线程的stack大小,难道是多个线程共用一个运行栈?
回复
wangk 2005-12-16
1. 如果一个对象的指针作为参数传入某个线程,如果线程通过这个指针调用对象的成员函数,那个成员函数中的 局部变量是不是在线程的运行栈中构造?

局部变量都是执行到函数体开始的时候初始化。另外线程是不拥有资源的,所有资源是进程的。

2.为什么相同的使用方式,CSingleLock没锁住,自写的却锁住了,是否Mutex句柄有属于那个线程的区别,CSingleLock是否可以递归锁定,说不清楚原因究竟在那里,所以希望大家能讨论一下.

我觉得应该是CSingleLock的原因,具体要看它的实现代码。

先Mark一下,有空再详细看看。
回复
wangk 2005-12-16
如果把上面的func改为
void func()
{
int mm[0xFFFFFFFF];
}
在运行的时候,应该会把thread所在线程的运行栈爆掉.
你够狠^_^ 我同意
回复
hdqqq 2005-12-16
谢谢楼上,我把CSingleLock的构造函数,和Lock,Unlock 的代码都看了,就是没有去看析构函数,原来是这个原因.

线程有运行的上下文,就是线程的stack,但是它里面所分配的内存,new出来的东西,如果没有指定特殊的堆(Heap)的话都在进程的默认堆里分配。一个进程可以有多个堆,但是线程没有堆。

上面的这个我是知道的.

我第一个问题的意思不是对于new 出来的东西. 而是成员函数中通过定义变量产生的对象实例,比如象下面

struct __tt
{
void func() {
int mm;
}
};

__tt g_t;

UINT thread(LPVOID param)
{
__tt* lp = (__tt*)param;
lp->func();
return 0;
}

void call_func()
{
afxbeginthread(thread,(void*)&g_t);
}

象上面的代码,线程thread在运行的时候,运行到lp->func();其中有一个局部变量 mm, 这个变量在运行上下文中是存在于某个运行栈中的,
我想讨论的是,这个mm是存在于thread这个线程的运行栈中,还是存在于主程序的运行栈中,因为g_t是个全局变量.

我当时是因为CSingleLock没有锁住,所以怀疑成员函数Lock中CSingleLock的局部变量实例没有位于线程的运行栈中,而是位于主线程的里面,导致锁定失败.

如果把上面的func改为
void func()
{
int mm[0xFFFFFFFF];
}
在运行的时候,应该会把thread所在线程的运行栈爆掉.

回复
wangk 2005-12-16
线程有运行的上下文,就是线程的stack,但是它里面所分配的内存,new出来的东西,如果没有指定特殊的堆(Heap)的话都在进程的默认堆里分配。一个进程可以有多个堆,但是线程没有堆。
回复
wangk 2005-12-16
OK,应该是你的CSingleLock使用有问题。
你把
template <class _data, class _mutex, class _lock>
class Mutex_Access_Adapter : public _data
{
private:
_mutex m_Mutex;
public:
BOOL Lock() {
_lock lock(&m_Mutex);
return lock.Lock();
}
BOOL Unlock() {
_lock lock(&m_Mutex);
return lock.Unlock();
}

public:
Mutex_Access_Adapter() {}
~Mutex_Access_Adapter() {}

};
直接改成
template <class _data, class _mutex, class _lock>
class Mutex_Access_Adapter : public _data
{
private:
_mutex m_Mutex;
public:
BOOL Lock() {
return m_Mutex.Lock();
}
BOOL Unlock() {
return m_Mutex.Unlock();
}

public:
Mutex_Access_Adapter() {}
~Mutex_Access_Adapter() {}

};
那么可以看见能够锁住。
第一个定义不能锁住的原因是CSingleLock在析构的时候自动解锁。在Afxmt.inl可以看到
_AFXMT_INLINE CSingleLock::~CSingleLock()
{ Unlock(); }
因此你不能在Lock函数里面使用局部CSingleLock变量。


回复
相关推荐
发帖
进程/线程/DLL
创建于2007-09-28

1.5w+

社区成员

VC/MFC 进程/线程/DLL
申请成为版主
帖子事件
创建了帖子
2005-12-16 11:30
社区公告
暂无公告