Windows提供了许多内核对象来实现线程的同步。对于线程同步而言,这些内核对象有两个非常重要的状态:“已通知”状态,“未通知”状态 (也有翻译为:受信状态,未受信状态)。Windows提供了几种内核对象可以处于已通知状态和未通知状态:进程、线程、作业、文件、控制台输入/输出 /错误流、事件、等待定时器、信号量、互斥对象
你可以通知一个内核对象,使之处于“已通知状态”,然后让其他等待在该内核对象上的线程继续执行。你可以使用Windows提供的API函数,等待函数来等待某一个或某些内核对象变为已通知状态。
你可以使用WaitForSingleObject函数来等待一个内核对象变为已通知状态:
DWORD WaitForSingleObject(
HANDLE hObject, //指明一个内核对象的句柄
DWORD dwMilliseconds); //等待时间
该函数需要传递一个内核对象句柄,该句柄标识一个内核对象,如果该内核对象处于未通知状态,则该函数导致线程进入阻塞状态;如果该内核对象处于已通知 状态,则该函数立即返回WAIT_OBJECT_0。第二个参数指明了需要等待的时间(毫秒),可以传递INFINITE指明要无限期等待下去,如果第二 个参数为0,那么函数就测试同步对象的状态并立即返回。如果等待超时,该函数返回WAIT_TIMEOUT。如果该函数失败,返回 WAIT_FAILED。可以通过下面的代码来判断:
DWORD dw = WaitForSingleObject(hProcess, 5000); //等待一个进程结束
switch (dw)
{
case WAIT_OBJECT_0:
// hProcess所代表的进程在5秒内结束
break;
case WAIT_TIMEOUT:
// 等待时间超过5秒
break;
case WAIT_FAILED:
// 函数调用失败,比如传递了一个无效的句柄
break;
}
还可以使用WaitForMulitpleObjects函数来等待多个内核对象变为已通知状态:
DWORD WaitForMultipleObjects(
DWORD dwCount, //等待的内核对象个数
CONST HANDLE* phObjects, //一个存放被等待的内核对象句柄的数组
BOOL bWaitAll, //是否等到所有内核对象为已通知状态后才返回
DWORD dwMilliseconds); //等待时间
该函数的第一个参数指明等待的内核对象的个数,可以是0到MAXIMUM_WAIT_OBJECTS(64)中的一个值。phObjects参数是一 个存放等待的内核对象句柄的数组。bWaitAll参数如果为TRUE,则只有当等待的所有内核对象为已通知状态时函数才返回,如果为FALSE,则只要 一个内核对象为已通知状态,则该函数返回。第四个参数和WaitForSingleObject中的dwMilliseconds参数类似。
该函数失败,返回WAIT_FAILED;如果超时,返回WAIT_TIMEOUT;如果bWaitAll参数为TRUE,函数成功则返回WAIT_OBJECT_0,如果bWaitAll为FALSE,函数成功则返回值指明是哪个内核对象收到通知。
可以如下使用该函数:
HANDLE h[3]; //句柄数组
//三个进程句柄
h[0] = hProcess1;
h[1] = hProcess2;
h[2] = hProcess3;
DWORD dw = WaitForMultipleObjects(3, h, FALSE, 5000); //等待3个进程结束
switch (dw)
{
case WAIT_FAILED:
// 函数呼叫失败
break;
case WAIT_TIMEOUT:
// 超时
break;
case WAIT_OBJECT_0 + 0:
// h[0](hProcess1)所代表的进程结束
break;
case WAIT_OBJECT_0 + 1:
// h[1](hProcess2)所代表的进程结束
break;
case WAIT_OBJECT_0 + 2:
// h[2](hProcess3)所代表的进程结束
break;
}
你也可以同时通知一个内核对象,同时等待另一个内核对象,这两个操作以原子的方式进行:
DWORD SignalObjectAndWait(
HANDLE hObjectToSignal, //通知的内核对象
HANDLE hObjectToWaitOn, //等待的内核对象
DWORD dwMilliseconds, //等待的时间
BOOL bAlertable); //与IO完成端口有关的参数,暂不讨论
该函数在内部使得hObjectToSignal参数所指明的内核对象变成已通知状态,同时等待hObjectToWaitOn参数所代表的内核对象。dwMilliseconds参数的用法与WaitForSingleObject函数类似。
该函数返回如下:WAIT_OBJECT_0,WAIT_TIMEOUT,WAIT_FAILED,WAIT_IO_COMPLETION。
等你需要通知一个互斥内核对象并等待一个事件内核对象的时候,可以这么写:
ReleaseMutex(hMutex);
WaitForSingleObject(hEvent, INFINITE);
可是,这样的代码不是以原子的方式来操纵这两个内核对象。因此,可以更改如下:
SignalObjectAndWait(hMutex, hEvent, INFINITE, FALSE);
===================================================================
对于多线程编程,我们有时候会遇到多个线程需要互斥访问同一个资源的问题,或者是线程间同步的问题,比如生存者和消费者,下面我就来讲讲多线程编程中的同步和互斥的问题。
1:互斥锁
当有一个链表,这个链表需要两个线程互斥访问时,我们就需要互斥锁。为什么呢?因为当一个线程要去使用这个链表时,首先他得先获得锁,一旦发现锁已经被别的线程占用,则无法获得锁将阻塞等待互斥锁被别人解锁,当然也有办法不阻塞,一旦无法获得锁,则直接返回。
如何初始化锁:
函数原型:
int pthread_mutex_init (pthread_mutex_t*mutex,constpthread_mutexattr_t* mutexattr);
函数传入值: mutex:互斥锁。
互斥锁有三种类型:
mutexattr:
PTHREAD_MUTEX_INITIALIZER 创建快速互斥锁。
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 创建递归互斥锁。
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 创建检错互斥锁。
区别如下:
互斥量分为下面三种:
1、快速型。这种类型也是默认的类型。该线程的行为正如上面所说的。
2、递归型。如果遇到我们上面所提到的死锁情况,同一线程循环给互斥量上锁,那么系统将会知道该上锁行为来自同一线程,那么就会同意线程给该互斥量上锁。
3、错误检测型。如果该互斥量已经被上锁,那么后续的上锁将会失败而不会阻塞,pthread_mutex_lock()操作将会返回EDEADLK。
使用方式如下:
pthread_mutex_t mutex;
pthread_mutex_init (&mutex, NULL); /*定义*/
...
pthread_mutex_lock(&mutex); /*获取互斥锁*/
临界资源
pthread_mutex_unlock(&mutex); /*释放互斥锁*/
此时对锁的访问时阻塞的。所以这就会出现这么一个问题,当同一个线程已经获得这个锁,并且没有释放这个锁,如果他在申请锁,将造成死锁。所以对于同一个线程获取了锁,记得要释放。不要因为其他原因导致释放锁没有被执行,而再次去请求锁。
2:条件变量
===============================================================
线程的同步和互斥
多线程共享一个进程的地址空间虽然线程间通信容易进行,但是多线程同时访问共享对象时需要引入同步和互斥机制。
同步指的是多个任务按照约定的顺序相互配合完成一件事情,dijkstra基于信号量的概念提出了一种同步机制。由信号量
赖决定线程是继续运行还是阻塞等待。
信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,智能通过三种操作来访问:初始化 P操作(申请资源),V操作(释放资源),信号量的值为非负整数
P操作的含义: if(信号量的值大于0)
{
申请资源的任务继续运行;
信号量的值减去1;
}
else
{
申请资源的任务阻塞;
}
v操作的含义: if(没有任务在等待该资源){信号量的值加1;}
else{唤醒第一个等待的任务,让其继续运行}
POSIX Semaphore API
posix 中定义了两类信号量:
无名信号量和有名信号量
pthread库常用的信号量操作函数如下:
int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_wait(sem_t *sem); P操作
int sem_post(sem_t * sem);V操作
int sem_trywait(sem_t *sem);
int sem_getvalue(sem_t *sem,int *svalue);
例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>
char buf[60];
sem_t sem;
void * function(void *arg);
int main()
{
pthread_t a_thread;
void * thread_result;
if(sem_init(&sem,0,0)<0)
{
perror("fail to sem_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)<0)
{
perror("fail to pthread_create");
exit(-1);
}
printf("input 'quit' to exit\n");
do
{
fgets(buf,60,stdin);
sem_post(&sem);
}while(strncmp(buf,"quit",4)!= 0);
return 0;
}
void * function(void *arg)
{
while(1)
{
sem_wait(&sem);
printf("YOu enter %d characters\n",strlen(buf)-1);
}
}
线程间的互斥,引入互斥锁的目的是用来保证共享资源数据操作的完整性。
互斥锁主要用来保护临界资源
每个邻居资源都由一个互斥锁来保护,任何时刻最多只能有一个线程能访问该资源。
线程必须先获得互斥锁才能访问临界资源,访问完临界资源后释放该锁。如果无法获得锁,线程会阻塞知道获得锁为止。
初始化互斥锁
所需头文件: #include<pthread.h>
函数原型;
int pthread_mutex_init(pthread_mutex_t * mutex,pthread_mutexattr_t *attr);
函数参数:mutex:互斥锁
attr: 互斥锁属性 //NULL表示缺省属性
函数返回值:成功 0,失败 -1
申请互斥锁:
函数原型:
int pthread_mutex_lock(pthread_mutex_t *mutex)
函数参数:
mutex 互斥锁
成功 0,失败 -1
释放互斥锁:
int pthread_mutex_unlock(pthread_mutex_t * mutex)
成功返回0,失败-1;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
int value1,value2;
unsigned int count = 0;
pthread_mutex_t mutex;
void * function(void *arg);
int main()
{
pthread_t a_thread;
void * thread_result;
if(pthread_mutex_init(&mutex,NULL) < 0)
{
perror("fail to mutex_init");
exit(-1);
}
if(pthread_create(&a_thread,NULL,function,NULL)<0)
{
perror("fail to pthread_create");
exit(-1);
}
while(1)
{
count++;
#ifdef _LOCK_
pthread_mutex_lock(&mutex);
#endif
value1 = count;
value2 = count;
#ifdef _LOCK_
pthread_mutex_unlock(&mutex);
#endif
}
return 0;
}
void * function(void *arg)
{
while(1)
{
#ifdef _LOCK_
pthread_mutex_lock(&mutex);
#endif
if(value1 != value2)
{
printf("count =%d,value1 =%d,value2=%d\n",count,value1,value2);
}
#ifdef _LOCK_
pthread_mutex_unlock(&mutex);
#endif
}
return NULL;
}
===============================================================
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源,如“第一类读写者模型”。
2Windows下的互斥同步机制
Windows下提供了多种内核对象实现线程、进程间的同步和互斥,常用的有:
1.
2.
3.
信号量常被用于保证对多个资源进行同步访问。
===============================================
几种进程间的通信方式
# 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
# 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
# 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
# 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
# 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
# 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
# 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。