多线程:
多线程是指在一个进程中,开启多个线程,这些线程根据一定的规则(或者无规则)进行抢夺cpu时间来进行,营造出一种并发处理的感觉,提高处理速度,同时也提高用户的体验,在cpu多核情况下,更能提高处理效率
多线程处理需要注意的地方:
1.对于处理同样工作的线程,需要处理好线程之间的逻辑。如生产者与消费者的问题中,消费者使用多线程提高消费速度,那么如何避免线程消费同样的消费品是需要考虑的。
2.对于处理不同工作的线程,需要考虑的是线程之间的联系问题,同样生产者与消费者的问题,生产者与消费者各用一个线程,那么必须要起码有一各生产者已经生产完,才能进行消费,如果消费完了,生产者还没生产下一件,需要挂起当前进程。
3.公共资源的问题,对于多线程,最麻烦也是最重要的就是考虑线程之间涉及的公共资源,此时需要进行加锁,设置临界区进行处理。具体问题需要进行具体分析:如公共资源,如果已经写完,只剩下读取操作,则不用加锁;又如对于vector<vector<string>> a
,每个线程对且仅对一个不同的元素进行操作,则也不用进行加锁,但如果处理元素有交叉,则要加锁,或者进入临界区处理。
4.线程的数量问题,对于多线程的程序而言,并不是线程越多效率越高,线程的创建以及销毁也是需要时间,此外对于需要多个短时间运行线程,可以考虑使用线程池来进行操作,避免过于频繁地创建线程。
windows多线程:
windows提供创建线程的函数:
//不推荐使用
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//安全设置,一般设为NULL
SIZE_T dwStackSize,// 初始栈大小(私有),设为0与主线程一致
LPTHREAD_START_ROUTINE lpStartAddress,//线程函数
LPVOID lpParameter, //线程传入参数 ,类型是void型指针
DWORD dwCreationFlags, //启动选项,0代表立即执行
LPDWORD lpThreadId // 线程ID,默认为NULL,线程的ID是唯一的
)
//推荐使用_beginThreadex()原因请看:
include<process.h>
_beginThreadex(
void*security,//安全设置,一般设为NULL
unsigned stack_size,// 初始栈大小(私有),设为0与主线程一致
unsigned ( __stdcall *start_address )( void * ),//线程函数,即函数名称
void *arglist, //线程传入参数 ,类型是void型指针
unsigned initflag,//启动选项,0代表立即执行
unsigned *thrdaddr// 线程ID,默认为NULL,线程的ID是唯一的
)
线程函数原型(多个线程可以共用一个线程函数):
void NTAPI FUN(
PTP_CALLBACK_INSTANCE Instance, // 回调函数的终止操作
PVOID Context //void*类型参数,可在函数内进行强制类型转换
)
***windows默认线程池**:
此外,windows还允许我们使用默认的线程池以及对默认线程池的环境进行设置(默认线程池中线程数量最多500,最小为1,这里只谈论默认线程池的调用方式)
让线程池执行一个请求:
BOOL TrySubmitThreadpoolCallback(
PTP_SIMPLE_CALLBACK pfnCallback, //线程函数
PVOID pvContext,//void*类型参数
PTP_CALLBACK_ENVIRON pcbe//线程池环境设置类型参数
)
但是,对于线程数量过多时,windows不建议我们使用上面的“一步式”申请线程的函数,因为此时有可能调用失败。windows建议我们先创建工作项,再提交工作项的方法来进行。
创建工作项:
PTP_WORK CreateThreadpoolWork(
PTP_SIMPLE_CALLBACK pfnCallback, //线程函数
PVOID pvContext,//void*类型参数
PTP_CALLBACK_ENVIRON pcbe//线程池环境设置类型参数
)
提交工作项
VOID SubmitThreadpoolWork(PTP_WORK);
对于提交上去的工作项,windows提供一个函数允许我们进行取消,或者等待其处理完成(相当于阻塞)
若工作项没提交,则立刻返回不执行操作;若参数为TRUE,函数会尝试取消工作项,但正在处理则等待完成后返回;若参数为FALSE,则会将调用此函数的线程挂起,直至工作项已处理完且对应线程也被收回并准备处理下一个工作项。
VOID WaitForThreadpoolWorkCallback(
PTP_WORK pWork,
BOOL bCancelPendingCallback
)
一般而言,对于要使用线程池,推荐使用boost 的thread库,里面已经封装好的,比windows默认的线程池好用很多。
多线程相关处理
临界区:访问临界资源的代码段称为临界区。临界区只允许一个线程(进程)进入,若已有线程在处理,其余线程必须等待。
CRITICAL_SECTION g_cs;//临界区变量的定义
InitializeCriticalSection(&g_cs);//初始化
EnterCriticalSection(&g_cs)//进入临界区
...//相关代码
LeaveCriticalSection(&g_cs)//离开临界区
原子操作:原子操作是指不会被线程调度机制打断的操作。以i++为例,i++在实际中会先进行读取变量,移到寄存器,寄存器加1,再返回设置,此时有可能会被其余线程打断,若i为公共资源,会产生难以想像的后果。
windows为我们提供了几个原子操作的函数:
LONG InterlockedDecrement(&LONG);//减1
LONG InterlockedIncrement(&LONG);//增1
//还有一些其他函数大家有兴趣可以搜搜。
//注意一般使用这种原子操作,最好使用返回值进行判断条件,可以避免多线程产生的问题
其他互斥锁,自旋锁,信号量这些博主暂时没具体使用过,有空再补~