
①线程在执行同步代码块或同步方法时,程序调用thread.sleep()方法,thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器的锁定。第4章 线程间通信概述 4.1 线程之间通信的方法 4.1.1 全局变量方式 4.1.2 参数传递法 4.1.3 消息传递法 4.1.4 通过同步变量进行线程间通信 4.2 线程间同步问题概述 4.3 死锁问题 4.4 本章小结。(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放。
以前做项目时,用过 Codeproject 上一个线程管理的代码 Work Queue[1],很好用,也是不错的学习资料,但对于多线程初学者也不是一眼就能看懂的,所以今天打算对这个代码做个解读笔记,可为其它学习者提供一个参考,也深化自己对多线程的理解。关于该类的使用可直接访问原网址。
这个多线程管理类为CWorkQueue,使用的是生产者-消费者模式。CWorkQueue 创建的每个线程都是一个消费者,生产者是类成员m_pWorkItemQueue。生产者资源由外界使用者通过InsertWorkItem 成员函数注入,然后通过ReleaseSemaphore 通知消费者(即线程)处理,消费者线程ThreadFunc 自创建起始就一直在等待,等待生产者通知,接到有任务通知后,线程就执行任务,执行完毕后继续等待。
主线程通过两种机制来跟已建立的新线程通信,信号量Semaphore 和 事件 Eventc 多线程 多任务,Semaphore 用于通知新线程执行任务,Event 用于通知新线程结果自己。
1、信号量Semaphore
由上可知,生产者和消费者使用的通信机制是信号量Semaphore。信号量Semaphore 只有两种状态,触发和未触发,决定状态的是当前资源数量,数量大于0表示信号量处于触发,等于0表示资源已经耗尽故信号量处于末触发。影响当前资源数量的函数有几个,下面依次介绍。

其实,不但创建队列实际调用通用队列创建函数,二进制信号量、计数信号量、互斥量和递归互斥量也都直接或间接使用这个函数,如表1-1所示。前面第21章,我们对计数信号量进行了讲解,计数信号量就是对一个变量进行计数,变量的范围是从0到用户创建计数信号量时所设置的大小。创建互斥量和创建信号量的api函数不同,但是共用获取和给出信号api函数。
m_phSincObjectsArray[SEMAPHORE_INDEX] = CreateSemaphore(NULL,0,LONG_MAX,NULL); //创建Semaphore对象
一般是将当前可用资源计数设置为最大资源计数 , 每增加一个线程对共享资源的访问 , 当前可用资源计数就会减 1 , 只要当前可用资源计数是大于 0 的 , 就可以发出信号量信号 。每当线程调用waitforsingleobject函数并传入一个信号量对象的句柄,系统将检查该信号量的资源计数是否大于0,如果大于0,系统就将资源计数减去1,并唤醒线程。其中,二进制信号量、计数信号量、互斥量和递归互斥量都是使用队列来实现的,因此掌握队列的运行机制,是很有必要的。
if (!ReleaseSemaphore(m_phSincObjectsArray[SEMAPHORE_INDEX],1,NULL)) { assert(false); return false; }
此函数检查指定的对象或事件的状态,如果该对象处于无信号状态,则调用线程处于等待状态,此时该线程不消耗cpu时间,。有很多封装的线程库中有这样的函数 condwait函数,这个函数的主要意义就是释放互斥量,等待信号量,然后再持有互斥量后返回,主要用于多线程运行状态下,等待一个信号通知,然后继续运行。在对信号量调用等待函数时,等待函数会检查信号量的当前资源计数,如果大于0(即信号量处于触发状态),减1后返回让调用线程继续执行。
//等待两个事件 dwWaitResult = WaitForMultipleObjects(NUMBER_OF_SYNC_OBJ,pWorkQueue->m_phSincObjectsArray,FALSE,INFINITE);

2、事件 Event
该函数创建线程,程结束的时候会开启新的任务,将事件添加进epoll关注,epoll_wait返回后就注销事件,并创建新的线程去处理这个事件。注意代码中的ostasksempend()函数就是和开头的信号发布相对应,当用户创建一个定时器(注意:在进行创建定时器时一定要先定义一个定时器变量一般都是全局的)osmrcreate()函数是用来创建一个定时器,创建完成后还需要在调用ostmrstart()来启动定时器,时内部调用的ostmrlink()函数将按照定时器剩余时间采用哈希算法将其加入定时器列表,其中ostmrcreate主要有这样几个变量需要注意一下。c、当其他线程调用pthread_cond_signal或pthread_cond_broadcast时,会唤醒相应条件变量等待的线程,此时被唤醒的线程,可以参与调度了,此时被唤醒的线程继续执行pthread_cond_wait()函数,函数pthread_cond_wait()返回之前,会重新给条件变量对应的互斥量上锁,在这里就是nready.mutex,若该函数成功返回,则当前线程有重新获得了nready.mutex锁,当然nready.mutex也可能被其他线程继续占有c 多线程 多任务,此时线程再次阻塞。
m_phSincObjectsArray[ABORT_EVENT_INDEX] = CreateEvent(NULL,TRUE,FALSE,NULL); //创建event 事件对象,初始化时为无信号状态,使用手动重置为无信号状态
该函数创建线程,程结束的时候会开启新的任务,将事件添加进epoll关注,epoll_wait返回后就注销事件,并创建新的线程去处理这个事件。调用时输入23和24.5,输入的这2个参数才是真正需要传递给函数的参数,ref int, ref double是告诉alien需要分配空间,调用c函数从栈中获取它的参数,调用结束后将返回结果放到栈中(为了区分返回结果和栈中的其他的值,每个c函数还会返回结果的个数),然后lua函数返回结果值。先说说用getmodulehandle函数获取.此方法需要用createremotethread函数在远程进程中创建线程,让该线程调用kernel32.dll中的getmodulehandle函数.但是只这样还不行.因为getmodulehandle函数需要以dll模块文件名做参数.既然是远程线程调用getmodulehandle函数.还需要先把dll模块文件名写入目标进程的地址空间中.最后用createremotethread函数创建线程执行getmodulehandle函数来获取dll句柄。
因为信号量和事件都是HANDLE,所以该类中把这两个存在一个数组m_phSincObjectsArray,通过枚举型变量来标识。
3、生产者m_pWorkItemQueue

用户通过InsertWorkItem 插入工作任务到队列。
bool CWorkQueue::InsertWorkItem(WorkItemBase* pWorkItem)
工作任务由要线程执行的函数,和 Abort 函数,Abort 函数用于最后调用 Destroy 突然终止线程时,执行剩余线程的清理工作。
class WorkItemBase { virtual void DoWork(void* pThreadContext) = 0; virtual void Abort () = 0; friend CWorkQueue; };
WorkItemBase 需要用户来继承,并实现两个虚函数。
class SpecificWorkItem : public WorkItemBase { void DoWork(void* pThreadContext); void Abort(); //memeber variables needed here }; void SpecificWorkItem::DoWork(void* pThreadContext) { //Notify Start //proccessing done here //Notify Finish //free all that was occupied } void SpecificWorkItem::Abort() { //Notify aborted //free all that was occupied }

这样在用户执行InsertWorkItem 插入任务到队列后,通过ReleaseSemaphore 激发信号量Semaphore,线程ThreadFunc 会在 WaitForMultipleObjects 中探测到Semaphore 的激发状态,然后获得队列中的任务,并删除队列中该任务防止其它线程重复执行,然后执行用户的任务pWorkItem->DoWork(pThreadData),此处pThreadData 我没用到,为 NULL 。
4、消费者ThreadFunc
消费者线程 ThreadFunc在Create 中创建,返回程句柄存储在类成员数组m_phThreads 中,供 Destroy 函数等待线程结束时使用。
DWORD dwThreadId; PTHREAD_CONTEXT pThreadsContext ; //创建所有的线程 for(i = 0 ; i < nNumberOfThreads ; i++ ) { //初始化每个线程的上下文,用于传递给线程函数 pThreadsContext = new THREAD_CONTEXT; pThreadsContext->pWorkQueue = this; //传递当前对象的指针,使新建线程可以访问到主线程中的生产者资源 pThreadsContext->pThreadData = ThreadData == NULL? NULL : ThreadData[i]; //创建线程 m_phThreads[i] = CreateThread(NULL, 0, CWorkQueue::ThreadFunc, pThreadsContext, 0, //为0表示线程创建之后立即就可以进行调度 &dwThreadId); }
5、由消费者线程去访问生产者资源可以看出,同一个进程的线程可共享内存。用户可以通过SpecificWorkItem 构造函数把数据和函数传进成员变量和成员函数,通过InsertWorkItem 把SpecificWorkItem 传进CWorkQueue 的m_pWorkItemQueue,即生产者任务队列,然后消费者线程会获得通知,并访问生产者提取任务,执行函数和数据。
参考资料:
1、
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-107162-1.html
是因为你真的真的值得爱