C++ 多线程

创建线程的API函数

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD:线程安全相关的属性,常置为NULL
SIZE_T dwStackSize,//initialstacksize:新线程的初始化栈的大小,可设置为0
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction:被线程执行的回调函数,也称为线程函数
LPVOID lpParameter,//threadargument:传入线程函数的参数,不需传递参数时为NULL
DWORD dwCreationFlags,//creationoption:控制线程创建的标志
LPDWORD lpThreadId//threadidentifier:传出参数,用于获得线程ID,如果为NULL则不返回线程ID
)

/*
lpThreadAttributes:指向SECURITY_ATTRIBUTES结构的指针,决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。

dwStackSize:设置初始栈的大小,以字节为单位,如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。
任何情况下,Windows根据需要动态延长堆栈的大小。

lpStartAddress:指向线程函数的指针,函数名称没有限制,但是必须以下列形式声明:
DWORD WINAPI 函数名 (LPVOID lpParam) ,格式不正确将无法调用成功。

lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。

dwCreationFlags:控制线程创建的标志,可取值如下:
(1)CREATE_SUSPENDED(0x00000004):创建一个挂起的线程(就绪状态),直到线程被唤醒时才调用
(2)0:表示创建后立即激活。
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000):dwStackSize参数指定初始的保留堆栈的大小,
如果STACK_SIZE_PARAM_IS_A_RESERVATION标志未指定,dwStackSize将会设为系统预留的值

lpThreadId:保存新线程的id

返回值:函数成功,返回线程句柄,否则返回NULL。如果线程创建失败,可通过GetLastError函数获得错误信息。

*/

BOOL WINAPI CloseHandle(HANDLE hObject); //关闭一个被打开的对象句柄
/*可用这个函数关闭创建的线程句柄,如果函数执行成功则返回true(非0),如果失败则返回false(0),
如果执行失败可调用GetLastError.函数获得错误信息。
*/

实例

//-----------------------------------------------------------------------------

#include <iostream>
#include <windows.h>

HANDLE hMutex = NULL;

DWORD WINAPI Fun001(LPVOID lpParamter)
{
for (int i = 0; i < 10; ++i)
{
WaitForSingleObject(hMutex, INFINITE);

std::cout << "Win Thread Fun2 Display!" << std::endl;
Sleep(100);

ReleaseMutex(hMutex);
}

return 0L;
}

DWORD WINAPI Fun002(LPVOID lpParamter)
{
for (int i = 0; i < 10; ++i)
{
WaitForSingleObject(hMutex, INFINITE);

std::cout << "Win Thread Fun1 Display!" << std::endl;
Sleep(100);

ReleaseMutex(hMutex);
}

return 0L;
}

void test_Win001()
{
hMutex = CreateMutex(NULL, FALSE, L"screen");

HANDLE hThread = CreateThread(NULL, 0, Fun001, NULL, 0, NULL);
hThread = CreateThread(NULL, 0, Fun002, NULL, 0, NULL);

CloseHandle(hThread);
}

//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
#include <mutex>

std::mutex g_mtx;

void fun001()
{
for (int i = 0; i < 10; ++i)
{
g_mtx.lock();

std::cout << "CPP Thread Fun1 Display!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));

g_mtx.unlock();
}
}

void fun002()
{
for (int i = 0; i < 10; ++i)
{
g_mtx.lock();

std::cout << "CPP Thread Fun2 Display!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(200));

g_mtx.unlock();
}
}

void test_CPP001()
{
std::thread thread1(fun001);
std::thread thread2(fun002);

thread1.join();
thread2.join();
}

//-----------------------------------------------------------------------------

可以看到主线程(main函数)和我们自己的线程(Fun函数)是随机交替执行的。可以看到Fun函数其实只运行了六次,这是因为主线程运行完之后将所占资源都释放掉了,使得子线程还没有运行完。看来主线程执行得有点快,让它sleep一下吧。

  使用函数Sleep来暂停线程的执行。

VOID WINAPI Sleep(
__in DWORD dwMilliseconds
);

dwMilliseconds表示千分之一秒,所以 Sleep(1000); 表示暂停1秒。

std::endl 修改为 "\n"

这时候,正如我们预期的,正确地输出了我们想要输出的内容并且格式也是正确的。在这里,我们可以把屏幕看成是一个资源,这个资源被两个线程所共用,加入当Fun函数输出了Fun Display!后,将要输出endl(也就是清空缓冲区并换行,在这里我们可以不用理解什么是缓冲区),但此时,main函数却得到了运行的机会,此时Fun函数还没有来得及输出换行(时间片用完),就把CPU让给了main函数,而这时main函数就直接在Fun Display!后输出Main Display!。

  另一种情况就是“输出两个换行”,这种情况就是比如输出Main Display!并输出endl后,时间片用完,轮到子线程占用CPU,子进程上一次时间片用完时停在了Fun Display!,下一次时间片过来时,刚好开始输出endl,此时就会“输出两个换行”。

  那么为什么我们把实例2改成实例3就可以正确的运行呢?原因在于,多个线程虽然是并发运行的,但是有一些操作(比如输出一整段内容)是必须一气呵成的,不允许打断的,所以我们看到实例2和实例3的运行结果是不一样的。它们之间的差异就是少了endl,而多了一个换行符\n。

  那么,是不是实例2的代码我们就不可以让它正确的运行呢?答案当然是否定的,下面我就来讲一下怎样才能让实例2的代码可以正确运行。这涉及到多线程的同步问题。对于一个资源被多个线程共用会导致程序的混乱,我们的解决方法是只允许一个线程拥有对共享资源的独占,这里我们用互斥量(Mutex)来进行线程同步。

HANDLE WINAPI CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, //线程安全相关的属性,常置为NULL
BOOL bInitialOwner, //创建Mutex时的当前线程是否拥有Mutex的所有权
LPCTSTR lpName //Mutex的名称
);
/*
MutexAttributes:也是表示安全的结构,与CreateThread中的lpThreadAttributes功能相同,表示决定返回的句柄是否可被子进程继承,如果为NULL则表示返回的句柄不能被子进程继承。
bInitialOwner:表示创建Mutex时的当前线程是否拥有Mutex的所有权,若为TRUE则指定为当前的创建线程为Mutex对象的所有者,其它线程访问需要先ReleaseMutex
lpName:Mutex的名称
*/

DWORD WINAPI WaitForSingleObject(
HANDLE hHandle, //要获取的锁的句柄
DWORD dwMilliseconds //超时间隔
);

/*
WaitForSingleObject:等待一个指定的对象(如Mutex对象),直到该对象处于非占用的状态(如Mutex对象被释放)或超出设定的时间间隔。除此之外,还有一个与它类似的函数WaitForMultipleObjects,它的作用是等待一个或所有指定的对象,直到所有的对象处于非占用的状态,或超出设定的时间间隔。

hHandle:要等待的指定对象的句柄。

dwMilliseconds:超时的间隔,以毫秒为单位;如果dwMilliseconds为非0,则等待直到dwMilliseconds时间间隔用完或对象变为非占用的状态,如果dwMilliseconds 为INFINITE则表示无限等待,直到等待的对象处于非占用的状态。
*/

BOOL WINAPI ReleaseMutex(HANDLE hMutex);
//说明:释放所拥有的互斥量锁对象,hMutex为释放的互斥量句柄

C++多线程

https://blog.csdn.net/MoreWindows/column/info/killthreadseries

反汇编
断点-->调试
调试->窗口->反汇编

//-----------------------------------------------------------------------------

#include <iostream>
#include <process.h>
#include <windows.h>

HANDLE hMutex = NULL;

// 登录次数
volatile long g_nLoginCount;

// 启动线程数
const int THREAD_NUM = 10;

unsigned int __stdcall ThreadFun003(void *pPM)
{
Sleep(100);
++g_nLoginCount;
Sleep(50);

return 0;
}

void test_Win003()
{
g_nLoginCount = 0;

HANDLE handle[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; ++i)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun003, NULL, 0, NULL);
}

WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM, g_nLoginCount);
}

//-----------------------------------------------------------------------------

// 启动线程数
const int THREAD_NUM_004 = 50;

DWORD WINAPI ThreadFun004(void *pPM)
{
Sleep(100);
++g_nLoginCount;
Sleep(50);

return 0;
}

void test_Win004()
{
printf("------原子操作 Interlocked系列函数的使用------\n");

//重复20次以便观察多线程访问同一资源时导致的冲突
int num = 20;
while (num--)
{
g_nLoginCount = 0;
int i;
HANDLE handle[THREAD_NUM_004];
for (i = 0; i < THREAD_NUM_004; ++i)
{
handle[i] = CreateThread(NULL, 0, ThreadFun004, NULL, 0, NULL);
}

WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);

printf("有%d个用户登录后记录结果是%d\n", THREAD_NUM_004, g_nLoginCount);
}
}

//-----------------------------------------------------------------------------

++g_nLoginCount;
00CA4DAF mov eax,dword ptr [g_nLoginCount (0D27570h)]
00CA4DB4 add eax,1
00CA4DB7 mov dword ptr [g_nLoginCount (0D27570h)],eax
第一条汇编将g_nLoginCount的值从内存中读取到寄存器eax中。
第二条汇编将寄存器eax中的值与1相加,计算结果仍存入寄存器eax中。
第三条汇编将寄存器eax中的值写回内存中。

由于线程执行的并发性,很可能线程A执行到第二句时,线程B开始执行,线程B将原来的值又写入寄存器eax中,这样线程A所主要计算的值就被线程B修改了。这样执行下来,结果是不可预知的——可能会出现50,可能小于50。

因此在多线程环境中对一个变量进行读写时,我们需要有一种方法能够保证对一个值的递增操作是原子操作——即不可打断性,一个线程在执行原子操作时,其它线程必须等待它完成之后才能开始执行该原子操作。

修改为

//g_nLoginCount++;
InterlockedIncrement((LPLONG)&g_nLoginCount);

最新文章

  1. Android GradientDrawable(shape标签定义) 静态使用和动态使用(圆角,渐变实现)
  2. Hibernate增删查改语句
  3. ArcGIS Server开发教程系列(7)使用ArcGIS API for Javascript-Hello World
  4. IE6 7下常见CSS兼容性处理
  5. Python--过滤Mysql慢日志
  6. 百度编辑器Ueditor自动换行,添加&lt;p&gt;的问题
  7. java 网络编程 模拟browser
  8. hdu4632Palindrome subsequence
  9. 谈&quot;http get和post的区别&quot;
  10. object-c 协议(Protocols)和代理(Delegation)的学习
  11. 为什么国外程序员爱用苹果Mac电脑?(转)
  12. C# 中的内存管理,摘自网络
  13. 音频自动增益 与 静音检测 算法 附完整C代码
  14. Adobe Audition 基本使用
  15. AGC030 简要题解
  16. Mybatis Generator的model生成中文注释,支持oracle和mysql(通过修改源码的方式来实现)
  17. 注解@RestController与@Controller的区别
  18. CMS垃圾收集器与G1收集器
  19. java 方法的返回类型
  20. ubuntu常用技巧积累

热门文章

  1. IIS反向代理解决Web前端跨域
  2. javascript 插件开发教程
  3. Nt函数原型
  4. ARM GNU 专有符号
  5. ASP.NET网站要手机自适应页面
  6. ECharts (mark)
  7. webstorm安装与破解
  8. java 数组常见操作
  9. MySQL学习 EXISTS的用法 转载
  10. W3C规范学习