多线程操作小结

/ 0评 / 0

Published by orzz.org(). (https://orzz.org/%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%93%8d%e4%bd%9c%e5%b0%8f%e7%bb%93/)

线程操作比较多也比较繁杂,线程创建之后就一直是一个活动的对象,管理起来也比较麻烦(尤其是在多线程同步的时候).

下面简单总结一下多线程中的各种操作和概念.

首先是如何创建多线程.线程需要一个入口,所以需要一个静态的函数指针作为线程入口.Win32下的入口函数定义如下:

然后再调用CreateThread就可以创建线程:

就算关闭了线程句柄线程本身也不会停止,只是外部没办法通过句柄操作线程.拿到线程句柄之后的一些操作:

在线程函数里的一些常用操作:

对于Win32,可以创建UI线程.创建方法是在线程函数内跑一个消息循环,如下:

上面是一个带消息循环的UI线程入口函数定义.我们可以在前面注册一个窗口类,这样消息就会被路由到窗口的回调函数中;也可以在消息循环中写回调转发消息.之后可以在线程外部通过API给线程里的窗口或者消息回调发送消息:

然后是如何做线程同步.线程同步的部分比较复杂,因为往往需要针对具体情况做具体分析,不存在通用的算法.比较高级的一些线程同步技术,如lock-free,讲起来也比较麻烦.下面只简单的整理一下相关概念,并给出一些例子.

线程同步的基本操作是加锁.锁有很多种类型,互斥量(Mutex);递归锁(Recursive lock);读写锁(Read-Write lock);旋转锁(Spin Lock)...下面是个多线程锁模型的例子:

_LockExchangePolicy是旋转锁,_LockCriticalPolicy是Win32中的临界区,属于可重入的递归锁.使用起来也很简单,在多个线程之外定义对象:

在需要加锁的时候,如某个数据对象需要被使用时调用Lock方法,使用完毕后调用Unlock:

读写锁定义比较复杂,貌似Win32中没有现成的单写多读锁可以用.示例实现如下:

使用上需要注意该读的时候调用读参数,该写的时候调用写参数.读的时候是不会加锁的,但是一旦有写操作,所有的其他读写操作都会被锁住.

实际使用中,这样的操作还是比较复杂,我们往往可以统一了上面的操作接口后,用自动化的方式帮助我们操作锁对象:

此时前面的锁抽象为基本的线程模型,由下面的锁对象负责调用.现在我们可以直接在需要加锁的位置这样写:

当过程结束时此锁会被自动释放.

然后就是事件对象,它不仅可以用来做类似锁的同步,还可以用来传递消息.基本实现如下:

首先定义同步对象接口:

然后定义事件对象:

通过设置一个事件对象,可以同时让多个线程恢复运行,锁就只能恢复一个线程的运行.

最后是信号量,相当与一个线程计数器.基本实现如下:

当需要控制访问某个数据对象的线程个数时,通过对一个CSemaphore对象做Release()操作,此时在这个CSemaphore上Wait的线程就会收到通知.当进入的线程个数达到Release的数量时,此时CSemaphore将阻止新的线程继续访问.

不管上面哪种线程同步的方法,其实本质就是在需要暂停线程的时候把线程挂起,在需要恢复运行的时候放行线程.比较高级一点的线程同步方式就是不对线程做挂起操作,而又能够保证数据的一致性,此时就需要使用无锁编程的相关技术,比如lock-free.

lock-free的话题在网络上已经讨论得比较深入了,其实现方式主要是依赖CAS原子操作:

对对象做循环检测,并在能够交换对象的时候,将改写之后的数据替换掉原来的旧数据.比如下面的一个多线程计数器(Counter)实现(参考:http://www.cnblogs.com/lucifer1982/archive/2009/04/08/1431992.html):

上面是一个简单的示例,往往在实际实现中需要配合引用计数指针或者GC来完成缓存数据部分的内存清理工作.

实际上各种不同的无锁编程方式,本质上就是依赖缓存的机制,对新的数据做缓存,并在老数据访问完毕的某个时间上替换数据访问点.由于问题本身的复杂性,无锁编程很难有统一的编程模型,往往只有针对具体需求做具体的编程工作才能得到比较优良的解.

Published by orzz.org(). (https://orzz.org/%e5%a4%9a%e7%ba%bf%e7%a8%8b%e6%93%8d%e4%bd%9c%e5%b0%8f%e7%bb%93/)

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据