C++自学笔记之多线程的创建与线程同步

PS:以下为个人理解与编写,打字好累~~~真的好累,不过这一写,就更不会忘记了,嘿嘿。。。 大笑
线程的创建:
那么如何创建线程呢? 可以通windows api里面的CreateThread函数创建一内核线程,该函数原形如下:
HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt __deref __drv_aliasesMem LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);
参数lpThreadAttributes是一个包含对象的安全描述,如果该参数为NULL,那么该线程使用默认安全,并且不可被子线程继承。
参数dwStackSize初始化栈的大小,如果为0,那么该大小与调用函数线程大小一致,windows在任何情况下,动态改变该大小。
参数lpStartAddress为线程的调用函数,这个函数必须是DWORD WINAPI ThreadProc(LPVOID lpvoid)类型,也可以是void型。
参数lpParameter为函数的参数,这个参数是一个结构体的指针,使用时需强制转换为该结构体类型的指针。
参数dwCreationFlags为函数的标示,如果为0,那么该线程创建后立即执行,如果不执行则标示为CREATE_SUSPENDED挂起,唤醒则调用ResumeThread函数。
参数lpThreadId为线程的ID。

如果该函数调用成功,则返回该线程的句柄Handle,否返回false。

CloseHandle函数,回收执行完毕的线程。

使用案例:
#include <Windows.h>
#include <conio.h>
#include <stdio.h>

DWORD WINAPI ThreadProc(LPVOID lparam);

struct ThreadParam
{
wchar_t message;
}

int main()
{
ThreadParam threadParam = {L"This is a ThreadParam"};
Handle thread = CreateThread(NULL,NULL,ThreadProc,&threadParam,CREATE_SUSPENDED,NULL);
ResumeThread(thread);
}

DWORD WINAPI ThreadProc(LPVOID lparam)
{
ThreadParam *threadParam = (ThreadParam *)lparam;
//.....................
}

也可以通过C++函数库里面的_beginThread函数来创建线程,这种方式比CreateThread简单,使用这个函数,需要头文件process.h
该函数原形如下:
uintptr_t __cdecl _beginthread
(
_In_ void (__cdecl * _StartAddress) (void *),
_In_ unsigned _StackSize,
_In_opt_ void * _ArgList
);
参数StartAddress为线程的调用函数,这个函数必须是void __cdecl ThreadProc(void*lparam);
参数StackSize为线程的堆栈大小,可以为0
参数ArgList为线程的参数列表

结束该线程用endthread();

使用案例:
#include <Windows.h>
#include <process.h>
#include <conio.h>
#include <stdio.h>

void __cdecl ThreadProc(void*lparam);

int main()
{
_beginthread(ThreadProc,0,NULL);
getch();
}

void __cdecl ThreadProc(void*lparam)
{
//..............
}

多线程的同步:
当多个线程访问同一个资源的时候,那么我们就需要同步,多线程的同步方式有以下几种:
一,临界区
二,管理事件内核对象
三,信号量
四,互斥内核对象

下面我们就一个一个来记录讲解:
临界区:处理公共数据的代码块,如果多个线程同时访问一个资源,那么只允许一个线程进入临界区,其它线程挂起等待,直到先前那个线程退出临界区,才让下一个线程进入,使用这种方式一般不允许执行时间过长,如果执行时间过长,那么其它线程就会一直挂起等待,从而一定程度上影响程序的运行性能,切记不要把外界因素加入到临界区域内,列如:等待用户输入等。

使用方法:需要创建一个临界区的结构体CRITICAL_SECTION,然后通过InitializeCriticalSection初始化结构体,最后在线程的临界区中加入EnterCriticalSection和LeaveCriticalSection函数即可

代码案例:
#include <Windows.h>
#include <process.h>
#include <conio.h>
#include <stdio.h>

int i = 100;
CRITICAL_SECTION critical;
DWORD WINAPI ThreadProc(LPVOID lparam);
DWORD WINAPI ThreadProc1(LPVOID lparam);

int main()
{
InitializeCriticalSection(&critical);
Handle handle = CreateThread(NULL,NULL,ThreadProc,NULL,0,NULL);
Handle handle1 = CreateThread(NULL,NULL,ThreadProc,NULL,0,NULL);
Sleep(5000);
CloseHandle(handle);
CloseHandle(handle1);
getch();
}

DWORD WINAPI ThreadProc(LPVOID lparam);
{
EnterCriticalSection(&g_ch);
while(true)
{
if (i<=0)
{
printf("线程一跳出了\n");
break;
}
i--;
printf("线程一执行中%d。。。。。\n",i);
}
LeaveCriticalSection(&g_ch);
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lparam);
{
EnterCriticalSection(&g_ch);
while(true)
{
if (i<=0)
{
printf("线程二跳出了\n");
break;
}
i--;
printf("线程二执行中%d。。。。。\n",i);
}
LeaveCriticalSection(&g_ch);
return 0;
}

上述代码,我们对i变量进行自减,其中两个线程操作这个变量,如果没有使用临界区,那么你就会看到两个线程的互争,即打印出来的结果非常乱,一会线程一,一会线程二,加上临界区 后,那么第一线程执行后(线程1和线程2都可能是第一个执行),那么第二个线程将会被挂起,等待第一个线程执行完毕后,第二个线程才执行,于是你就会看到打印结果非常整齐,而第二个线程则只会输出一句话而已。

管理事件内核对象(一):事件内核对象可以通过通知操作的方式来保持线程的同步。作用与临界区是一样的,只不过使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,前提是得到对此事件对象的访问权。

使用方法:需要调用CreateEvent函数创建一个事件,该函数返回Handle,然后调用SetEvent函数,最后在线程的开始处加入WaitForSingleObject函数结尾处加入SetEvent函数即可。

代码案例:
对上面案例稍作修改即可:
#include <Windows.h>
#include <process.h>
#include <conio.h>
#include <stdio.h>

int i = 100;
HANDLE hEvent;
DWORD WINAPI ThreadProc(LPVOID lparam);
DWORD WINAPI ThreadProc1(LPVOID lparam);

int main()
{
hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
SetEvent(hEvent);
Handle handle = CreateThread(NULL,NULL,ThreadProc,NULL,0,NULL);
Handle handle1 = CreateThread(NULL,NULL,ThreadProc,NULL,0,NULL);
Sleep(5000);
CloseHandle(handle);
CloseHandle(handle1);
getch();
}

DWORD WINAPI ThreadProc(LPVOID lparam);
{
WaitForSingleObject(hEvent,INFINITE);
while(true)
{
if (i<=0)
{
printf("线程一跳出了\n");
break;
}
i--;
printf("线程一执行中%d。。。。。\n",i);
}
SetEvent(hEvent);
return 0;
}
DWORD WINAPI ThreadProc1(LPVOID lparam);
{
WaitForSingleObject(hEvent,INFINITE);
while(true)
{
if (i<=0)
{
printf("线程二跳出了\n");
break;
}
i--;
printf("线程二执行中%d。。。。。\n",i);
}
SetEvent(hEvent);
return 0;
}

管理事件内核对象(二) :如果一个线程需要等待其它两个或多个线程执行完成后才能执行,那么我们就将用到WaitForMultipleObjects函数,比如:C线程的执行条件是A线程和B线程执行完毕后才执行。这个函数的第三个参数
如果为TRUE,那么就表示其它线程全部执行完毕后才执行线程C,如果为False,那么它表示其它线程有一个执行完毕那
么它就执行线程C。如果需要获取具体那个线程执行状态,可以通过WAIT_OBJECT_0获取。

代码案例:
以下案例注释掉WaitForMultipleObjects这句,然后多运行几次,看看结果,在加上WaitForMultipleObjects运行几次看看结果
#include <Windows.h>
#include <process.h>
#include <conio.h>
#include <stdio.h>

HANDLE handle[2];

DWORD WINAPI A(LPVOID lparam);
DWORD WINAPI B(LPVOID lparam);
DWORD WINAPI C(LPVOID lparam);

int main()
{
handle[0] = CreateThread(NULL,NULL,A,NULL,0,NULL);
handle[1] = CreateThread(NULL,NULL,B,NULL,0,NULL);
CreateThread(NULL,NULL,C,NULL,0,NULL);
getch();
}
DWORD WINAPI A(LPVOID lparam)
{
printf("我是线程A\n");
}

DWORD WINAPI B(LPVOID lparam)
{
printf("我是线程B\n");
}

DWORD WINAPI C(LPVOID lparam)
{
HANDLE h = WaitForMultipleObjects(2,handle,TRUE,INFINITE);
printf("我是线程C\n");
//如果为改成false,表示当线程B执行完毕,就执行线程C
//HANDLE h = WaitForMultipleObjects(2,handle,FALSE,INFINITE)
//if(h == WAIT_OBJECT_0)
// {
// printf("我是线程C\n");
// }
}

信号量内核对象:这种方式与前面两种有所不同,它允许多个线程同时访问,但需要限制最大访问数。

使用方法:与前面的一样,首先还是创建CreateSemaphore信号量,该函数还是返回Handle,然后再线程开始处加入WaitForSingleObject函数,结尾处加上ReleaseSemaphore函数即可,代码与上面一样,修改哈就行了,这里就不举
列子了,以下给出函数原形,其中CreateSemaphore(NULL,2,2,NULL)意思就是可以同时运行两个线程。

CreateSemaphore函数原形:
HANDLE CreateSemaphore(
 LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
 LONG lInitialCount, // 初始计数
 LONG lMaximumCount, // 最大计数
 LPCTSTR lpName // 对象名指针
);
ReleaseSemaphore函数原形:
BOOL ReleaseSemaphore(
 HANDLE hSemaphore, // 信号量句柄
 LONG lReleaseCount, // 计数递增数量
 LPLONG lpPreviousCount // 先前计数
);


互斥内核对象:它能够保证多个线程对同一共享资源的互斥访问。同临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。

使用方法: 与前面的基本相同,创建CreateMutex互斥对象,该函数还是返回Handle,然后再线程开始处加入WaitForSingleObject函数,结尾处加上ReleaseMutex函数即可,以下给出函数原形,案例代码与上面一样,修改哈就行了,这里就不举列子了。

CreateMutex函数原型为:
HANDLE CreateMutex(
 LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
 BOOL bInitialOwner, // 初始拥有者
 LPCTSTR lpName // 互斥对象名
); 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值