十二多线程
多线程
信号驱动I/0和异步I/0的区别
信号驱动I/0是指进程预先告知内核,使得当某个描述字上发生某事时,内核使用信号通知相关进程。
异步I/0是进程执行I/0系统调用(比如读或者写),内核启动I/0操作后立刻返回进程,进程可以在I/0操作执行期间继续处理别的事情,然后当I/0操作成功或者失败时,内核以进程预先设定的方式通知进程。
使用锁保护同步数据的原则
多个线程使用同一个数据,必须保护该数据;
如果多个线程共用一个基础类型变量,应该声明为volatile,防止编译器使用将其缓存到寄存器内的优化方式;
共享的整数变量应该优先使用原子操作来修改其值;
有时候,可以考虑将某些需要共享的数据只让一个单独线程处理,然后将该线程作为一种服务提供给其他线程使用;
如果有很多数据需要在线程间共享,可以考虑使用数据库来管理这些数据。
不确定的情况下,使用锁来保护。
Readers/Writers锁
假设有很多线程共享一个变量,只有一个(或者很少的)线程负责修改这个变量的值,其他(或者大多数的)线程只是读取变量的值。如果采用常规思路,所有线程访问该变量都必须获得一个锁,这样的效率是很低的。因为如果写变量的线程没有执行对该变量的修改操作时,其他的读线程理论上都可以安全的读该变量的值。常规思路的解决方案让所有的读线程都排队,所以我们需要readers/writers锁来提供更好的效率。
在readers/writers锁的解决方案里,只要没有任何线程在修改共享变量的值,所有其他线程都可以读取该变量的值,如果有一个线程正在修改共享变量的值,其他任何线程无论读取还是改写的操作都必须等待获得锁。这种解决方案适用于在写操作较少,读操作较多的多线程环境中。
readers/writers锁有两种策略,读优先或者写优先策略。读优先策略是指总是让读操作先完成,写优先是指只要有正在执行的或者等待的写操作,读操作就需要等待写操作完成以后才能执行。图书馆书籍查找系统通常使用读操作,而航班预定系统由于强调数据的有效性,采用写优先策略。
ACE提供了跨平台的两个类ACE_RW_Thread_Mutex和ACE_RW_Process_Mutex。
前者提供了线程间的同步,后者提供了进程范围的同步。这两个类采用了写优先策略。ACE的readers/writer锁仅适用于读操作较多而写操作较少的多线程环境,注意在大多数其他情况下,readers/writers锁都比采用mutex的常规思路慢。
信号量
(摘自http://blog.yesky.com/134/polaris0161/1555634.shtml)
信号量是一种Linux的资源,它可以让不同的进程间进行相互通信,因此它也被看作是IPC集中的一员。信号量的作用是在两个或多个进程访问公共资源集时 保持同步。
信号量是一个计数器的值,它可以被几个进程作为一个集合以原子的方式执行。信号量的计数器控制着对资源的访问控制,信号量提供了两个主要的操作来处理计数器的值:
(1)资源的使用者在使用资源之前等待信号量。如果信号量的值为0,则继续等待,如果大于0,则将信号量值减1,使用者开始使用资源。
(2)资源的使用者在资源使用完毕后通知信号量。使用者通知信号量不再使用资源了,信号量的值加1,检查等待信号量的使用者的序列,以确定是否有其他的使用者在等待之中。
使用信号量进行进程间的通信一般牵涉到以下操作:
相关信号量的操作函数一般应该包含下面的头文件;
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
1.创建信号量:在程序使用信号量之前,必须首先创建信号量。创建信号量的函数原形如下所示:
int semget(key_t key,int nsems,int semflg);
参数说明:
(1)key:在本地系统中表示要创建或者访问的信号量集的关键字,当然为了避免与其他的信号量产生冲突,我们可以简单的利用IPC_PRIVATE来表示一个新建的信号量。
(2)nsems:要创建或者要访问的信号量集中信号量的数目。
(3)指定不同的选项和权限位的标志。可以为IPC_CREATE,IPC_EXCL.
创建一个新的信号量:
int semid;
semid=semget(0x1234,2,IPC_CREATE|IPC_EXCL|0600);
if(semid<0)
{
process error case.
}else
{
do the next thing;
}
使用已经存在的信号量:
int semid;
semid=semget(0x1234,0,0);
if(semid<0)
{}else
{}
2.初始化信号集:
int semctl(int semid,int semnum,int cmd,union semun arg);
union semun
{
int val;
struct semid_ds *buf;
ushort *array;
};
eg:
int semid;
int z;
union semun arg;
ushort initv[]={4,2};
arg.array=initv;
z=semctl(semid,2,SETALL,arg);
3.等待通知信号量:等待信号量和通知信号量都是利用函数semop(),只是相关的参数不同而已。
int semop(int semid,struct sembuf *sops,unsigned nsops);
struct sembuf
{
short sem_num; /*信号量的索引*/
short sem_op; /*信号量的相关操作为一个整数(正数表示通知信号量,负数表示等待信号量)*/
short sem_flg;/*相关的操作标记*/
};
eg:
int z;
static struct sembuf sops[]=
{
{1,-1,0},
{0,-1,0}
};
z=semop(semid,sops,2);
如果是通知信号量,则sembuf的值可以设为
static struct sembuf sops[]=
{
{1,+1,0},
{0,+1,0}
};
z=semop(semid,sops,2);
4.删除信号量
当信号量在系统中没有用处后,应该将其删除,这样才能释放内核中的支持表所占用的资源。
int semctl(int semid,int semnum,int cmd,union semun arg);
eg:
int semid;
int z;
union semun arg;
z=semctl(semid,0,IPC_RMID,arg);
值得一提的是信号量的使用遵循自愿的原则的,也就是说信号量并不能强制的限制应用程序对资源的访问,我们在编写每一个程序的时候,都将通过资源的原则来执行如下操作:
1)在访问受控资源前等待信号量。
2)释放受控的资源后要通知信号量。