windows 多线程

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/include_heqile/article/details/100088902

文章参考自https://www.bogotobogo.com/cplusplus/multithreading_win32A.php

下面的代码使用三种方式创建线程

#include <Windows.h>
#include <process.h>
#include <stdio.h>

DWORD WINAPI mythreadA(__in LPVOID lpParameter)
{
	printf("CreateThread %d \n", GetCurrentThreadId());
	return 0;
}

unsigned int __stdcall mythreadB(void* data)
{
	printf("_beginthreadex %d \n", GetCurrentThreadId());
	return 0;
}

void mythreadC(void* data)
{
	printf("_beginthread %d \n", GetCurrentThreadId());
}

int main(int argc, char* argv[])
{
	HANDLE myhandleA, myhandleB, myhandleC;

	myhandleA = CreateThread(0, 0, mythreadA, 0, 0, 0);

	myhandleB = (HANDLE)_beginthreadex(0, 0, &mythreadB, 0, 0, 0);
	WaitForSingleObject(myhandleB, INFINITE);


	myhandleC = (HANDLE)_beginthread(&mythreadC, 0, 0);
	getchar();

	return 0;
}

三种创建方式,第一种是使用CreateThread函数,没什么好讲的

主要说一下_beginthread_beginthreadex这两个函数之间的区别

  • 函数原型不一样

_beginthread:

uintptr_t _beginthread( 
   void( *start_address )( void * ),
   unsigned stack_size,
   void *arglist 
);

_beginthreadex:

uintptr_t _beginthreadex( 
   void *security,
   unsigned stack_size,
   unsigned ( *start_address )( void * ),
   void *arglist,
   unsigned initflag,
   unsigned *thrdaddr 
);
  • 创建的线程运行的函数calling convention和返回值不一样
    前者没有返回值,且calling convention是__cdecl,后者的返回值是unsigned intcalling convention__stdcall

作为参数传入的函数的calling convention不一样是因为_beginthread_beginthreadex在定义的时候使用了不同的calling convention进行修饰

看一下上面代码中的mythreadCmyThreadB就一目了然了

虽然这两个函数的返回值是uintptr_t,但是在实际使用的时候需要强制转换为HANDLE类型才可以进行后续的使用

在上面的代码中,我们使用了WaitForSingleObject函数,我们传入了两个参数,前者是一个handle,后者是超时时间,该函数会将handle加载到一个线程上执行,设置为INFINITE表示会一直等待到该线程terminate

一旦main函数所在的线程terminate了,那么他所创建的所有线程也会随之terminate,为了避免有些子线程还未执行完成就被强制terminate,可以调用WaitForMultipleObjects函数

扫描二维码关注公众号,回复: 7194943 查看本文章

相比较_beginthread函数,使用_beginthreadex更安全一点,因为前者在thread结束后,会自行清理掉handle,考虑一种情况:
使用_beginthread创建的线程很快就运行完毕并terminate掉了,这时该函数返回的handle已经被清理掉了,那么这时返回的handle就不是我们刚刚创建的thread的handle了,可能是指向其他thread的无效handle

当线程结束的时候,_endthread函数和_endthreadex被自动调用来关闭掉_beginthread_beginthreadex函数创建的线程,但是这两个函数的不同之处在于_endthread函数会自动调用CloseHandle函数来处理掉handle,后者则不会,我们必须手动调用CloseHandle才能清理掉_beginthreadex函数返回的handle

以上就是为啥_beginthreadex要比_beginthread函数相对安全一些的原因

下面是一个WaitForMultipleObjects函数的使用示例:

#include <Windows.h>
#include <process.h>
#include <stdio.h>

unsigned int __stdcall mythread(void* data) 
{
	printf("Thread %d\n", GetCurrentThreadId());
	return 0;
}

int main(int argc, char* argv[])
{
	HANDLE myhandle[2];

	myhandle[0] = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0);
	myhandle[1] = (HANDLE)_beginthreadex(0, 0, &mythread;, 0, 0, 0);

	WaitForMultipleObjects(2, myhandle, true, INFINITE);

	CloseHandle(myhandle[0]);
	CloseHandle(myhandle[1]);
	getchar();

	return 0;
}

第一个参数表示要等待的线程的数量,第二个参数是指向handle数组的指针,第三个参数是BOOL型的,true表示要等待所有的线程终止之后该函数才会返回,false表示只要handle数组中有一个线程结束就返回,最后一个参数是超时时间

线程的同步问题

mutex

使用互斥对象进行线程间的同步

当我们有多个线程需要同时访问同一个资源时,我们便可以使用mutex来进行同步,防止资源被同时访问

使用CreateMutex函数来创建mutex对象,当其中一个线程需要对资源进行访问时,需要先使用WaitForSingleObject函数来请求获取mutex,如果此时mutex没有任何线程占用,那么该线程就会拥有该mutex,在对资源进行了一系列的必要操作之后,该线程使用ReleaseMutex函数释放对mutex的所有权

示例程序

#include <windows.h>
#include <stdio.h>

#define THREADCOUNT 2

HANDLE ghMutex; 

DWORD WINAPI WriteToDatabase( LPVOID );

int main( void )
{
    HANDLE aThread[THREADCOUNT];
    DWORD ThreadID;
    int i;

    // Create a mutex with no initial owner

    ghMutex = CreateMutex( 
        NULL,              // default security attributes
        FALSE,             // initially not owned
        NULL);             // unnamed mutex

    if (ghMutex == NULL) 
    {
        printf("CreateMutex error: %d\n", GetLastError());
        return 1;
    }

    // Create worker threads

    for( i=0; i < THREADCOUNT; i++ )
    {
        aThread[i] = CreateThread( 
                     NULL,       // default security attributes
                     0,          // default stack size
                     (LPTHREAD_START_ROUTINE) WriteToDatabase, 
                     NULL,       // no thread function arguments
                     0,          // default creation flags
                     &ThreadID); // receive thread identifier

        if( aThread[i] == NULL )
        {
            printf("CreateThread error: %d\n", GetLastError());
            return 1;
        }
    }

    // Wait for all threads to terminate

    WaitForMultipleObjects(THREADCOUNT, aThread, TRUE, INFINITE);

    // Close thread and mutex handles

    for( i=0; i < THREADCOUNT; i++ )
        CloseHandle(aThread[i]);

    CloseHandle(ghMutex);

    return 0;
}

DWORD WINAPI WriteToDatabase( LPVOID lpParam )
{ 
    // lpParam not used in this example
    UNREFERENCED_PARAMETER(lpParam);

    DWORD dwCount=0, dwWaitResult; 

    // Request ownership of mutex.

    while( dwCount < 20 )
    { 
        dwWaitResult = WaitForSingleObject( 
            ghMutex,    // handle to mutex
            INFINITE);  // no time-out interval
 
        switch (dwWaitResult) 
        {
            // The thread got ownership of the mutex
            case WAIT_OBJECT_0: 
                __try { 
                    // TODO: Write to the database
                    printf("Thread %d writing to database...\n", 
                            GetCurrentThreadId());
                    dwCount++;
                } 

                __finally { 
                    // Release ownership of the mutex object
                    if (! ReleaseMutex(ghMutex)) 
                    { 
                        // Handle error.
                    } 
                } 
                break; 

            // The thread got ownership of an abandoned mutex
            // The database is in an indeterminate state
            case WAIT_ABANDONED: 
                return FALSE; 
        }
    }
    return TRUE; 
}

运行效果:
在这里插入图片描述

Critical Section Objects

相比其他的同步方式,Critical Section Objects提供了一种轻量级的高效快速的解决同步问题的方法,和互斥变量相似,一个Critical Section Objects在同一时间只能被一个线程所拥有,这样一来就可以保护一个共享资源不被同时访问,但是和mutex objects不同的是,你无从得知Critical Section Objects是否被abandon(所谓abandon,就是线程已经结束,但是却没有释放mutex objects)了

我们可以使用这两个函数(EnterCriticalSection、TryEnterCriticalSection )之一来获取Critical Section Objects,使用LeaveCriticalSection来释放Critical Section Objects

当我们尝试获取Critical Section Objects时,如果使用EnterCriticalSection,则会一直等待直到Critical Section Objects被其他的线程释放,相比mutex objects的获取函数,少了一个超时时间的设置,如果使用TryEnterCriticalSection,若此时Critical Section Objects的所有权在其他线程那里,则该函数根本不会等待而是立刻返回

其实我现在还不太清楚TryEnterCriticalSection函数的使用场景

猜你喜欢

转载自blog.csdn.net/include_heqile/article/details/100088902