使用CreateThread创建线程执行回调函数造成参数内容改变(线程不安全)的问题探讨

问题描述:

使用C/C++ 利用Win32 API 函数启动线程,其启动线程的函数原型为:

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)
这里能看到,第三个参数实际上是函数指针,第四个参数是线程传递参数时候用作参数传递的参数,从形式上看,我们能提出一个问题:

如果启动线程池,做一个for或者while循环,如果我们想要循环执行同一个函数,通过赋给这个函数不同的实参,来达到多线程完成若干个任务的目的,那么,这个目的是否可以通过我们这么操作来完成呢?

从原理上来看,貌似可以,但是,如果一个线程当前没有访问另一个正在执行该函数的线程的许可证时,也就是说,当两个或者多个线程,同时调用该函数时,就可能发生一个问题:

如果,这若干个线程都同时执行一个函数,并且传递不同的参数,这个参数就有可能被改写!

也就是说,使用C++的这种多线程的写法,是一种内存不安全的写法!

如果前一个启动的线程的任务还没有完成, 他使用的参数的内容就被改写了,这个错误是十分严重的。

 我们来看一个例子:

用C++来实现一个服务端套接字,在main函数(主线程)中执行 “取回用户”,也就是阻塞状态的accept,主要代码如下》》

	std::cin >> port;//端口号
	m_server *ser = new m_server(port);//写的一个类,创建套接字,主要起到监听目的
	while(1)//死循环,在主线程中,用于实时取回客户
	{
		ser->m_listen(&rec);//当取回客户后,执行回调函数,用作处理客户上线“事件”
	}
	system("pause");
	return 0;
我们在m_listen成员函数中写入代码,启动线程、、目的是:当用户上线之后,主线程启动各个子线程,用于接收各个客户发送的数据。》》

if ((clientSocket = accept(serverSocket, (sockaddr*)&clientAddress, &addrlen)) == INVALID_SOCKET) 
	{
		printf("接受客户端连接失败!");
		return -1;
	}
	else
	{
		
		online.push_back(clientSocket);//加入vector中,与此同时,启动一个线程
		HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)Run,&clientSocket, 0, NULL);
                //启动线程,传递的参数是&clientSocket,当前取回的上线用户
		cout << (int)&clientSocket << "--" << (int)Run << endl;//输出内存地址,便于对比
(*func)(&clientSocket, &clientAddress);//执行回调函数}
分别用两个不同的客户连接这个服务器,并且发送数据。

使用CreateThread创建线程执行回调函数造成参数内容改变(线程不安全)的问题探讨_第1张图片

接受到的服务端的样子:

使用CreateThread创建线程执行回调函数造成参数内容改变(线程不安全)的问题探讨_第2张图片

可以看到,传递的参数都是相同的,是因为,在内存的堆中,他们的内存位置是一样的,

解决办法是动态创建线程,类似于java中的new出一个新的类,用runnable或thread,不会占用同一个内存位置。

C++中也有类似的写法,网上有类似的教程,只不过标准库没有而已了。

还有就是用WIN32 API的时候,要注意下这个问题,当然,如果函数可以动态做出一个copy更好了。。方法不唯一,所以,一般用CPP做多线程的时候,也都不推荐直接使用CreatThread.

因为这种方法本质上是线程不安全的,如果不涉及到相同内存区域的改写,使用这个方法倒是可以考虑,否则,如果需要线程安全的场景,一定要考虑到内存区域位于堆上所造成的副作用。

你可能感兴趣的:(C++与C)