现在的位置: 首页 > 综合 > 正文

Win32多线程编程(3) — 线程同步与通信

2013年06月07日 ⁄ 综合 ⁄ 共 2572字 ⁄ 字号 评论关闭

一.线程间数据通信

系统从进程的地址空间中分配内存给线程栈使用。新线程与创建它的线程在相同的进程上下文中运行。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同一个进程中其他所有线程的栈。这样一来,同一个进程中的多个线程可以很容易的相互通信。

到目前为止,将数据从一个线程传到另一个线程的惟一方法是在创建线程时传递给新线程一个指针参数(LPVOID lpParam)。参数lpParamLPVOID指针类型,我们可在其中存储普通的数值(size为平台地址总线宽度),也可以存放指向某个数据结构(structclass)的地址。在新线程函数中,解引用时需要强制类型转换回原类型,以进行正确的访问。

以下代码段演示了一个典型的多线程场景。

// A typical multithread scene

DWORD WINAPI FirstThread(PVOID lpParam)

{

    // Initialize a stack-based variable

    int x = 0;

    DWORD dwThreadID;

   

    // Create a new thread.

    HANDLE hThread = CreateThread(NULL, 0, SecondThread, (LPVOID)&x, 0, &dwThreadID);

   

    // We don't reference the new thread anymore,

    // so close our handle to it.

    CloseHandle(hThread);

   

    // Our thread is done.

    // BUG:our stack will be destroyed,

    // but SecondThread might try to access it.

    return 0;

}

 

DWORD WINAPI SecondThread(LPVOID lpParam)

{

    // Do some lengthy processing here.

    // ...

    // Attempt to access the variable on FirstThread's stack.

    // NOTE:This may cause an access violation - it depends on timing!

    *((int*)lpParam) = 5;

    // ...

    return 0;

}

上述场景中,Windows没有维持线程之间的“父子关系“,即父线程FirstThread已经终止运行,而子线程SecondThread仍在继续运行。以上父子关系只是为了一种解说上的方便,实际上FirstThreadSecondThread具有相同的优先级(默认是 normal),因此它们“同时”执行。这样,FirstThread在开辟SecondThread后,不等SecondThread运行至代码*((int*)lpParam) = 5;即可能退出。FirstThread栈上的自动变量x已销毁,而SecondThread试图去访问之,将导致Access Violation。这是多线程编程中新手常犯的错误。

解决以上问题,大概有以下三种方案。

1)让创建线程等待新线程退出后才退出。在FirstThread代码CloseHandle(hThread);之前WaitForSingleObject(hThread, INFINITE);这样保证SecondThread中对FirstThread栈中自动变量x的访问有效期。

2)将x声明为堆变量,即int *px = new int;,在SecondThread中对px进行访问完毕后调用delete px;释放堆内存。由于堆内存对进程有效,因此,上述代码中FirstThread先退出,在SecondThread中对px的访问依然有效,直到进程的某处将该内存delete掉。这是在需要动态创建线程参数(数据结构)时的一种解决方案,实际应用中经常用到。

3x声明为静态变量static int x = 0;则将存储在静态存储区域。这里有全局和局部之分,若在FirstThread之前声明,则整个程序均可显式访问x若在FirstThread之中声明,则x只在FirstThread中可见。当然这里传址给SecondThreadSecondThread可按址访问。

在方案(3)中,若FirstThread之中将x声明为静态变量,将使函数SecondThread不可重入。换言之,不能创建两个使用相同线程函数的线程,因为这两个线程将共享同一个静态变量。这涉及到下文将要阐述的线程同步问题。

二.多线程同步互斥问题

1.同步问题的导入

多个线程共享数据时,同时读没有问题,但如果同时读和写,情况就不同了。

在本次线程内读取一个变量时,为提高存取速度,编译器优化时,有时会先把变量读取到一个寄存器中;以后取变量值时,就直接从寄存器中取值;当变量值在本线程中改变时,会同时把变量的新值拷贝到该寄存器中,以便保持一致;当变量在因别的线程等而改变了值,该寄存器的值不会相应改变,从而造成应用程序读取的值和实际的变量值不一致。

// CountError

#include <stdio.h>

#include <windows.h>

#include <process.h>

 

int g_nCount1 = 0;

int g_nCount2 = 0;

BOOL g_bContinue = TRUE;

 

UINT __stdcall ThreadFunc(LPVOID);

 

int main(int argc, char* argv[])

{

    UINT uId;

    HANDLE h[2];

   

    h[0] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId);

    h[1] = (HANDLE)::_beginthreadex(NULL, 0, ThreadFunc, NULL, 0, &uId);

 

    // 等待1秒后通知两个计数线程结束,关闭句柄

    Sleep(1000);

    g_bContinue = FALSE;

 

     // 等待两个线程都运行完

    ::WaitForMultipleObjects(2, h, TRUE, INFINITE);

抱歉!评论已关闭.