进程与线程(Linux、windows)

进程是操作系统结构的基础,是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合中运行的过程,它是系统进行资源分配和调度的一个独立单位。对于进程来所,其最重要的结构是进程控制块(详细只能各位去了解了)。

对于Linux,进程是简单,就是通过fork()函数,其他没啥好说的;

对于windows,在window xp中,由于几乎不用考虑权限的问题,所以直接使用window api函数CreateProcess调用就行了;但在vista、windows7下用CreateProcess函数存在第一次运行权限问题,所以必须得高考提权与降权的问题(网上有人可以调用 SheelExecute以执行命令方式执行可以避免).

其中,CreateProcess创建当前进程的子进程,可以继承当前进程的内核句柄;Winexec/ShellExecute可以创建与当前进程不同地址空间的进程;

以下是我上司根据网上的资料写,大家可参考一下

SECURITY_ATTRIBUTES sa; 
HANDLE hReadPipe,hWritePipe; 

sa.nLength = sizeof(SECURITY_ATTRIBUTES); 
sa.lpSecurityDescriptor = NULL; 
sa.bInheritHandle = TRUE; 

//创建管道
BOOL bRet = CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
if(!bRet)
	return 0;

//控制命令行窗口信息
STARTUPINFO si; 
//返回进程信息
PROCESS_INFORMATION pi; 

si.cb = sizeof(STARTUPINFO); 
GetStartupInfo(&si); 
si.hStdError = hWritePipe; 
si.hStdOutput = hWritePipe; 
si.wShowWindow = SW_HIDE; //隐藏命令行窗口
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;

wchar_t szPath[_MAX_PATH]={0};
SystemService::getExecutePath(szPath);

CString szCmd;
szCmd.Format(_T("icacls %s /setintegritylevel M"),szPath);
//创建获取命令行进程
bRet = ::CreateProcess(NULL, szCmd.GetBuffer(0), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi ); 
(个人觉得使用 SheelExecute会更合适)

线程是进程中某个单一顺序的控制流,亦称为轻量进程,是运行中的程序的调度单位。对于线程来所,其最重要的是用于控制线程运行的线程控制块ICB(详细只能各位去了解了)。

Linux:http://www.cnblogs.com/forstudy/archive/2012/04/05/2433853.html

线程标识
  线程ID
    •进程ID在整个系统中是唯一的
    •线程ID只在它所属的进程环境中有效
函数: pthread_self()

线程标识
  pthread_t类型通常用结构来表示
  •不能把它作为整数处理
    –Linux使用无符号长整数表示
  •为了移植,使用函数来比较线程ID
函数: pthread_equal()


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int main(){
    pthread_t thread_id;

    thread_id=pthread_self(); // 返回调用线程的线程ID
    printf("Thread ID: %lu.\n",thread_id);

    if (pthread_equal(thread_id,pthread_self())) {
//    if (thread_id==0) {
        printf("Equal!\n");
    } else {
        printf("Not equal!\n");
    }
    return 0;
}

线程编程
  操作用户空间中的线程
 
创建线程
  •调用该线程函数的入口点
  •使用函数pthread_create(),线程创建后,就开始运行相关的线程函数

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *thrd_func(void *arg);
pthread_t tid;

int main(){
    // 创建线程tid,且线程函数由thrd_func指向,是thrd_func的入口点,即马上执行此线程函数
    if (pthread_create(&tid,NULL,thrd_func,NULL)!=0) {
        printf("Create thread error!\n");
        exit(1);
    }

    printf("TID in pthread_create function: %u.\n",tid);
    printf("Main process: PID: %d,TID: %u.\n",getpid(),pthread_self()); 
    
    sleep(1); //race

    return 0;
}

void *thrd_func(void *arg){
//    printf("I am new thread!\n");
    printf("New process:  PID: %d,TID: %u.\n",getpid(),pthread_self()); //why pthread_self
    printf("New process:  PID: %d,TID: %u.\n",getpid(),tid); //why pthread_self

    pthread_exit(NULL); //退出线程
//    return ((void *)0);
} 

退出线程
  •在线程函数运行完后,该线程也就退出了
  •或使用函数pthread_exit(),这是线程的主动行为
  •不能使用exit()

使调用进程终止,所有线程都终止了

等待线程

  •由于一个进程中的多个线程是共享数据段的,通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放

  •pthread_join()函数

    类似进程的wait()/waitpid()函数,用于将当前线程挂起来等待线程的结束
    是一个线程阻塞的函数,调用它的线程一直等待到被等待的线程结束为止
    函数返回时,被等待线程的资源就被收回


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *thrd_func1(void *arg);
void *thrd_func2(void *arg);

int main(){
    pthread_t tid1,tid2;
    void *tret;
    // 创建线程tid1,线程函数thrd_func1
    if (pthread_create(&tid1,NULL,thrd_func1,NULL)!=0) {
        printf("Create thread 1 error!\n");
        exit(1);
    }
    // 创建线程tid2,线程函数thrd_func2
    if (pthread_create(&tid2,NULL,thrd_func2,NULL)!=0) {
        printf("Create thread 2 error!\n");
        exit(1);
    }
    // 等待线程tid1结束,线程函数返回值放在tret中
    if (pthread_join(tid1,&tret)!=0){
        printf("Join thread 1 error!\n");
        exit(1);
    }

    printf("Thread 1 exit code: %d.\n",(int *)tret);
    // 等待tid2结束,线程函数返回值放在tret中
    if (pthread_join(tid2,&tret)!=0){
        printf("Join thread 2 error!\n");
        exit(1);
    }

    printf("Thread 2 exit code: %d.\n",(int *)tret);

    return 0;
}

void *thrd_func1(void *arg){
    printf("Thread 1 returning!\n");
//    sleep(3);
    return ((void *)1); // 自动退出线程
}

void *thrd_func2(void *arg){
    printf("Thread 2 exiting!\n");
    pthread_exit((void *)2);  // 线程主动退出,返回(void *)2
}


取消线程

  •在别的线程中要终止另一个线程
  •pthread_cancel()函数
  •被取消的线程可以设置自己的取消状态
    –被取消的线程接收到另一个线程的取消请求之后,是接受还是忽略这个请求
    –如果接受,是立刻进行终止操作还是等待某个函数的调用等


#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

void *thrd_func1(void *arg);
void *thrd_func2(void *arg);

pthread_t tid1,tid2;

int main(){
    // 创建线程tid1,线程函数thrd_func1
    if (pthread_create(&tid1,NULL,thrd_func1,NULL)!=0) {
        printf("Create thread 1 error!\n");
        exit(1);
    }
    // 创建线程tid2,线程函数thrd_func2
    if (pthread_create(&tid2,NULL,thrd_func2,NULL)!=0) {
        printf("Create thread 2 error!\n");
        exit(1);
    }
    // 等待线程tid1退出
    if (pthread_join(tid1,NULL)!=0){
        printf("Join thread 1 error!\n");
        exit(1);
    }else
        printf("Thread 1 Joined!\n");
    // 等待线程tid2退出
    if (pthread_join(tid2,NULL)!=0){
        printf("Join thread 2 error!\n");
        exit(1);
    }else
        printf("Thread 2 Joined!\n");

    return 0;
}

void *thrd_func1(void *arg){
//    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL);
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL); // 设置其他线程可以cancel掉此线程

    while(1) {
        printf("Thread 1 is running!\n");
        sleep(1);
    }
    pthread_exit((void *)0);
}

void *thrd_func2(void *arg){
    printf("Thread 2 is running!\n");
    sleep(5);
    if (pthread_cancel(tid1)==0)  // 线程tid2向线程tid1发送cancel
        printf("Send Cancel cmd to Thread 1.\n");
        
    pthread_exit((void *)0);
}

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THREAD_NUM 3
#define REPEAT_TIMES 5
#define DELAY 4

void *thrd_func(void *arg);

int main(){
    pthread_t thread[THREAD_NUM];
    int no;
    void *tret;
    
    srand((int)time(0)); // 初始化随机函数发生器 

    for(no=0;no<THREAD_NUM;no++){
        if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) { // 创建THREAD_NUM个线程,传入(void*)no作为thrd_func的参数
            printf("Create thread %d error!\n",no);
            exit(1);
        } else
            printf("Create thread %d success!\n",no);
    }

    for(no=0;no<THREAD_NUM;no++){
        if (pthread_join(thread[no],&tret)!=0){ // 等待thread[no]线程结束,线程函数返回值放在tret中
            printf("Join thread %d error!\n",no);
            exit(1);
        }else
            printf("Join thread %d success!\n",no);
    }
        
    return 0;
}

void *thrd_func(void *arg){
    int thrd_num=(void*)arg;
    int delay_time=0;
    int count=0;

    printf("Thread %d is starting.\n",thrd_num);
    for(count=0;count<REPEAT_TIMES;count++) {
        delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1;
        sleep(delay_time);
        printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time);
    }

    printf("Thread %d is exiting.\n",thrd_num);
    pthread_exit(NULL);
}


线程同步与互斥
  线程共享进程的资源和地址空间,对这些资源进行操作时,必须考虑线程间同步与互斥问题
  三种线程同步机制
    •互斥锁
    •信号量
    •条件变量
  互斥锁更适合同时可用的资源是惟一的情况
    信号量更适合同时可用的资源为多个的情况 

 

 

互斥锁
  用简单的加锁方法控制对共享资源的原子操作
  只有两种状态: 上锁、解锁
可把互斥锁看作某种意义上的全局变量
  在同一时刻只能有一个线程掌握某个互斥锁,拥有上锁状态的线程能够对共享资源进行操作
  若其他线程希望上锁一个已经被上锁的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止
互斥锁保证让每个线程对共享资源按顺序进行原子操作

互斥锁分类
  区别在于其他未占有互斥锁的线程在希望得到互斥锁时是否需要阻塞等待
  快速互斥锁
    •调用线程会阻塞直至拥有互斥锁的线程解锁为止
    •默认为快速互斥锁
  检错互斥锁
    •为快速互斥锁的非阻塞版本,它会立即返回并返回一个错误信息
 
互斥锁主要包括下面的基本函数:
  互斥锁初始化:pthread_mutex_init()
  互斥锁上锁:pthread_mutex_lock()
  互斥锁判断上锁:pthread_mutex_trylock()
  互斥锁解锁:pthread_mutex_unlock()
  消除互斥锁:pthread_mutex_destroy()
 

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define THREAD_NUM 3
#define REPEAT_TIMES 5
#define DELAY 4

pthread_mutex_t mutex;

void *thrd_func(void *arg);

int main(){
    pthread_t thread[THREAD_NUM];
    int no;
    void *tret;
    
    srand((int)time(0));
       // 创建快速互斥锁(默认),锁的编号返回给mutex    
    pthread_mutex_init(&mutex,NULL);

    // 创建THREAD_NUM个线程,每个线程号返回给&thread[no],每个线程的入口函数均为thrd_func,参数为
    for(no=0;no<THREAD_NUM;no++){
        if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) {
            printf("Create thread %d error!\n",no);
            exit(1);
        } else
            printf("Create thread %d success!\n",no);
    }
    
    // 对每个线程进行join,返回值给tret
    for(no=0;no<THREAD_NUM;no++){
        if (pthread_join(thread[no],&tret)!=0){
            printf("Join thread %d error!\n",no);
            exit(1);
        }else
            printf("Join thread %d success!\n",no);
    }
    // 消除互斥锁
    pthread_mutex_destroy(&mutex);
    return 0;
}

void *thrd_func(void *arg){
    int thrd_num=(void*)arg; // 传入的参数,互斥锁的编号
    int delay_time,count; 
    
    // 对互斥锁上锁
    if(pthread_mutex_lock(&mutex)!=0) {
        printf("Thread %d lock failed!\n",thrd_num);
        pthread_exit(NULL);
    }

    printf("Thread %d is starting.\n",thrd_num);
    for(count=0;count<REPEAT_TIMES;count++) {
        delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1;
        sleep(delay_time);
        printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time);
    }

    printf("Thread %d is exiting.\n",thrd_num);
    // 解锁
    pthread_mutex_unlock(&mutex);
    
    pthread_exit(NULL);
}

和上一版本的程序差异在于有没有锁,有锁的情况下,必须等"thread x is exiting."之后其他线程才能继续。

 

信号量
  操作系统中所用到的PV原子操作,广泛用于进程或线程间的同步与互斥
    •本质上是一个非负的整数计数器,被用来控制对公共资源的访问
  PV原子操作:对整数计数器信号量sem的操作
    •一次P操作使sem减一,而一次V操作使sem加一
    •进程(或线程)根据信号量的值来判断是否对公共资源具有访问权限
  –当信号量sem的值大于等于零时,该进程(或线程)具有公共资源的访问权限
  –当信号量sem的值小于零时,该进程(或线程)就将阻塞直到信号量sem的值大于等于0为止
 
PV操作主要用于线程间的同步和互斥
  互斥,几个线程只设置一个信号量sem
  同步,会设置多个信号量,安排不同初值来实现它们之间的顺序执行

 

信号量函数
  sem_init() 创建一个信号量,并初始化它
  sem_wait()和sem_trywait(): P操作,在信号量大于零时将信号量的值减一
    •区别: 若信号量小于零时,sem_wait()将会阻塞线程,sem_trywait()则会立即返回
  sem_post(): V操作,将信号量的值加一同时发出信号来唤醒等待的线程
  sem_getvalue(): 得到信号量的值
  sem_destroy(): 删除信号量

 

eg. 同步各线程,执行顺序为逆序。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>

#define THREAD_NUM 3
#define REPEAT_TIMES 5
#define DELAY 4

sem_t sem[THREAD_NUM];

void *thrd_func(void *arg);

int main(){
    pthread_t thread[THREAD_NUM];
    int no;
    void *tret;
    
    srand((int)time(0)); 

    // 初始化THREAD_NUM-1个信号量,均初始化为0
    for(no=0;no<THREAD_NUM-1;no++){
        sem_init(&sem[no],0,0);
    }

    // sem[2]信号量初始化为1,即sem数组中最后一个信号量
    sem_init(&sem[2],0,1);
    
    // 创建THREAD_NUM个线程,入口函数均为thrd_func,参数为(void*)no
    for(no=0;no<THREAD_NUM;no++){
        if (pthread_create(&thread[no],NULL,thrd_func,(void*)no)!=0) {
            printf("Create thread %d error!\n",no);
            exit(1);
        } else
            printf("Create thread %d success!\n",no);
    }
    
    // 逐个join掉THREAD_NUM个线程
    for(no=0;no<THREAD_NUM;no++){
        if (pthread_join(thread[no],&tret)!=0){
            printf("Join thread %d error!\n",no);
            exit(1);
        }else
            printf("Join thread %d success!\n",no);
    }
    
    // 逐个取消信号量
    for(no=0;no<THREAD_NUM;no++){
        sem_destroy(&sem[no]);
    }

    return 0;
}

void *thrd_func(void *arg){
    int thrd_num=(void*)arg; // 参数no
    int delay_time,count;

    // 带有阻塞的p操作
    sem_wait(&sem[thrd_num]);

    
    printf("Thread %d is starting.\n",thrd_num);
    for(count=0;count<REPEAT_TIMES;count++) {
        delay_time=(int)(DELAY*(rand()/(double)RAND_MAX))+1;
        sleep(delay_time);
        printf("\tThread %d:job %d delay =%d.\n",thrd_num,count,delay_time);
    }

    printf("Thread %d is exiting.\n",thrd_num);
    
    // 对前一个信号量进行V操作
    // 由于只有最后一个信号量初始化为1,其余均为0
    // 故线程执行的顺序将为逆序
    sem_post(&sem[(thrd_num+THREAD_NUM-1)%THREAD_NUM]);

    pthread_exit(NULL); // 线程主动结束
}


Windows: 
Win32多线程创建方法主要有:
1、CreateThread()
2、_beginthread()和_beginthreadex()
3、AfxBeginThread()
4、CWinThread类
说明:CreateThread()是操作系统的API;_beginthreadex是C/C++运行时库函数,其中_beginthread是_beginthreadex的子集,_beginthreadex内部调用CreateThread()函数AfxBeginThread()与CWinThread类属于MFC;

注意:如果你编程只调用Win32 API/SDK,就放心用CreateThread()函数;如果要用到C/C++运行时间库,那么就要使用_beginthreadex()函数(个人觉得,在windows平台,采用_beginthreadex()函数生成会更好)

插入一个话题,为什么说使用_beginthreadex()函数会更好呢?相信网上都有答案,我在这里就重复一遍了。其原因是:_beginthreadex函数在创建线程的时候分配了一个堆结构并和线程本身关联起来,我们把这个结构叫做tiddata结构,是通过线程本地存储器TLS与线程本身关联起来。我们传入的线程入口函数就保存这个结构中。tiddata的作用除了保存线程函数入口地址之外,还有一个重要的作用就是保存和获取C运行库中的一些数据,比如说errno之类的线程全局变量。当线程调用要求tiddata结构的运行时库函数时,运行时库函数试图获取线程数据块的地址。如果没有获取到,函数就会现场分配一个tiddata结构,并与线程相关联。此时,问题就出现了。如果不同过_endthreadex()函数来终结线程的话,这个结构将不会被撤销,就会导致内存泄露(其中_endthreadex()函数里面包含了ExitThread()函数调用)。但一般情况,推荐使用自动退出线程体的方式。所以,如果才用CreateThread()函数创建线程,其线程分配的tiddata结构将不会与线程关联,这就导致内存泄露咯。

这个需要tiddata结构的函数有点麻烦,在侯捷《win32多线程程序设计》一书中说到到:

如果在除主线程之外的任何线程中进行一下操作,你就应该使用多线程版本的C runtime library,并使用_beginthreadex和_endthreadex:
1 使用malloc()和free(),或是new和delete
2 使用stdio.h或io.h里面声明的任何函数
3 使用浮点变量或浮点运算函数
4 调用任何一个使用了静态缓冲区的runtime函数,比如:asctime(),strtok()或rand()

首先看Win32函数:

创建一个线程:

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD描述施行与这一新线程的security属性,NULL表示使用缺省值,在windows 95中忽略该参数
  DWORD dwStackSize,                        // initial stack size新线程拥有的堆栈大小,0表示缺省大小,1MB
  LPTHREAD_START_ROUTINE lpStartAddress,    // thread function  函数指针
  LPVOID lpParameter,                       // thread argument  传递到线程函数的参数
  DWORD dwCreationFlags,                    // creation option  允许产生一个暂时挂起的线程,默认为立即运行
  LPDWORD lpThreadId                        // thread identifier  //新的线程ID会被传回到这里
);
关闭线程句柄:

BOOL CloseHandle(
  HANDLE hObject   // handle to object
);
判断线程是否结束:

BOOL GetExitCodeThread(
  HANDLE hThread,      // handle to the thread
  LPDWORD lpExitCode   // termination status
);
强制结束一个线程:

VOID ExitThread(
  DWORD dwExitCode   // exit code for this thread 指示此线程之结束代码
);
还有等待其他线程结束的函数: WaitForSingleObject

对于CRT的函数,只介绍创建与退出:

创建一个线程:

uintptr_t _beginthreadex (

  void *security, //Pointer to a SECURITY_ATTRIBUTES structure that determines whether 

                  //the returned handle can be inherited by child processes. 

                  //If NULL, the handle cannot be inherited.

  unsigned stack_size,  //Stack size for a new thread or 0

  unsigned ( *start_address )( void * ), //Start address of a routine that begins execution 

                                         //of a new thread. The calling convention is 

                                         //__stdcall or __clrcall

  void *arglist,  //Argument list to be passed to a new thread or NULL

  unsigned initflag,  //Initial state of a new thread (0 for running or CREATE_SUSPENDED 

                      //for suspended); use ResumeThread to execute the thread

  unsigned *thrdaddr  //Pointer to address that receives the thread identifier. Might be null

);
退出一个线程:

void _endthreadex( unsigned retval );
注意:

与_beginthread相比:
1)创建的线程可以处于suspended状态,可以指定安全级别,可以通过threadID进行访问.
2)线程调用的函数必须有返回值
3)当线程创建失败时,返回0
4)函数返回的handle可以被用于同步的API.
5)需要在线程结束后,显示的调用CloseHandle.


Windows同步机制:
1)临界区(Critical Section),也称关键代码段

临界区对象依赖一个CRITICAL_SECTION数据结构记录一些信息,确保在同一时间只有一个线程访问该数据段中的数据。有如下操作函数:

使用临界区对象必须得初始化:

VOID InitializeCriticalSection( LPCRITICAL_SECTION lpCriticalSection  /* critical section */ );

进入临界区中的数据时候得申请进入权限:
VOID EnterCriticalSection( LPCRITICAL_SECTION lpCriticalSection /* critical section */ );

使用完临界区的把使用权限还给Windows系统:
VOID LeaveCriticalSection( LPCRITICAL_SECTION lpCriticalSection /* critical section */ );

不使用得释放临界区资源:

VOID DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection /* critical section*/ );
临界区对象能够很好地保存共享数据,但是它不能够用于进程之间资源的锁定。由于它不是内核对象,故临界区只能用于在同一线程内的线程同步。如果要在进程间维持线程的同步,可以使用事件内核对象。

2)事件(Event),亦是事件内核对象

主线程在创建工作线程时,需要彼此之间有信息通信。此时,事件内核对象是一种比较好的通信方法。事件对象(Event)是一种抽象的对象,它也有未受信(nonsignaled)和受信(signaled)两种状态。也可以使用WaitForSingleObject/WaitForMultipleObjects函数等待其变成受信状态。不同于其他内核对象,系统提供的一些API可以使事件对象在这两种状态之间转化。可以把事件对象看成是一个设置在Windows内部的标志,它的状态设置和测试工作由Windows来完成。

事件对象包含 3 个成员:nUsageCount(使用计数)bManualReset(是否人工重置)和bSignaled(是否受信)。成员nUsageCount记录了当前的使用计数,当使用计数为 0 的时候,Windows 就会销毁此内核对象占用的资源;成员bManualReset指定在一个事件内核对象上等待的函数返回之后,Windows是否重置这个对象为未受信状态;成员bSignaled 指定当前事件内核对象是否受信。下面要介绍的操作事件内核对象的函数会影响这些成员的值。(网上讲解的)

使用事件对象得去创建:

// The CreateEvent function creates or opens a named or unnamed event object.
HANDLE CreateEvent(
                 LPSECURITY_ATTRIBUTES lpEventAttributes, // SD
                 BOOL bManualReset,                       // reset type
                 BOOL bInitialState,                      // initial state
                 LPCTSTR lpName                           // object name
);

参数一为事件对象的安全属性,一般填充NULL表示取默认值。
参数二,选择事件对象的重置方式以决定类型。bManualReset = TRUE则表示人工重置(manual-reset);bManualReset = FALSE则表示自动重置(auto-reset)。当一个人工重置的事件对象受信以后,所有等待在这个事件上的线程都会变为可调度状态;当一个自动重置的事件对象受信以后,Windows 仅允许一个等待在该事件上的线程变成可调度状态,然后就自动重置此事件对象为未受信状态。通常使用自动重置的事件内核对象,即设置bManualReset = FALSE
参数三bInitialState对应着 bSignaled 成员的初态。若将它设为TRUE,则表示事件对象创建时的初始状态为受信;若将它设为FALSE,则初始状态为未受信。通常设置初始状态为未受信,即置bInitialState= FALSE
参数四指定事件对象的名称,以便跨进程按名访问。

跨进程访问事件内核对象,可传入事件对象名调用OpenEvent函数该对象句柄:

// The OpenEvent function opens an existing named event object.
HANDLE OpenEvent(
               DWORD dwDesiredAccess,  // access
               BOOL bInheritHandle,    // inheritance option
               LPCTSTR lpName          // object name
);

系统创建或打开一个事件内核对象后,会返回事件的句柄。当不使用此内核对象时,应该调用 CloseHandle函数释放所占用的资源

事件对象建立后,可以通过SetEventResetEvent函数来设置状态:

// The SetEvent function sets the specified event object to the signaled state.
BOOL SetEvent(
             HANDLE hEvent   // handle to event
);
// The ResetEvent function sets the specified event object to the nonsignaled state.
BOOL ResetEvent(
              HANDLE hEvent   // handle to event
);
SetEventResetEvent相关是 PulseEvent函数,所谓 PulseEvent即瞬间Set/Reset:
// The PulseEvent function sets the specified event object to the signaled state and then resets it to 
//the nonsignaled state after releasing the appropriate number of waiting threads.
BOOL PulseEvent( HANDLE hEvent /* handle to event object */ );
事件内核对象的同步,代码上主要体现对 WaitForSingleObject / WaitForMultipleObjects函数的调用,等待事件对象的置信。事件对象是一个用于线程间通信,被广泛使用的内核对象。因为它是一个内核对象,所以可以跨进程使用。依靠在线程间通信就可以使各线程的工作协调进行,达到同步的目的。

3)信号量(Semaphore)
创建信号量内核对象:

// The CreateSemaphore function creates or opens a named or unnamed semaphore object.
HANDLE CreateSemaphore(
                     LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // SD
                     LONG lInitialCount,          // initial count
                     LONG lMaximumCount,          // maximum count
                     LPCTSTR lpName                // object name
                     );

CreateSemaphore函数创建信号量时,参数三指定允许的最大资源计数,参数二指定当前可用的初始资源计数。一般将lInitialCount设置与lMaximumCount相等。参数四即内核对象名称,以便跨进程执行OpenSemaphore按名访问。

只要当前可用资源计数大于0,就可以发出信号量信号,在该信号量上的等待函数调用WaitForSingleObject返回。每增加一个线程对共享资源的访问,当前可用资源计数就会减1。WaitForSingleObject返回后,调用线程在对共享资源的同步处理完毕后,应调用ReleaseSemaphore来增加当前可用资源计数。否则,将会出现当前正在处理共享资源的实际线程并没有达到要限制的数值,而其他线程却因为当前可用资源计数为0而仍无法进入的情况。

// The ReleaseSemaphore function increases the count of the specified semaphore object by a specified amount.
BOOL ReleaseSemaphore(
                    HANDLE hSemaphore,       // handle to semaphore
                    LONG lReleaseCount,      // count increment amount
                    LPLONG lpPreviousCount   // previous count
                    );

参数一为信号量内核对象句柄;参数二为计数递增值,一般设为1,当然也可以按需要设置大于1的值;参数三为之前的计数,往往填NULL,当然可指定导出到当地变量。

信号量内核对象的同步,代码上体现在对WaitForSingleObject/WaitForMultipleObjects函数的调用,其同步条件为资源计数大于0,即有可用资源。某个线程处理完共享资源后,需要调用ReleaseSemaphore释放资源,增加可用资源计数。当然,同其他内核对象一样,最终也得调用CloseHandle函数释放信号量内核对象占用的资源。

信号量的使用特点使其更适用于对Socket程序中线程的同步问题。一个典型的场景就是HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这是可以为每一个用户对服务器的页面请求设置一个线程,而页面则是待保护的资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设置的最大用户数目的线程能够进行访问,而其他访问企图被挂起。只有在有用户退出对此页面的访问后,其他用户的访问请求才有可能得到响应。

迅雷的“原始地址线程数”即是设置客户端从某一原始地址下载资源的最大线程数。当然,资源所在的站点本身就会对某一客户连接数有限制,这里的“某一客户连接数”意即把文件拆开,一个线程下载一块的多线程协助下载。

当然,多线程并不是越多越好,迅雷下载肯定使用的是线程池。迅雷将下载线程数限制为5,符合线程池的经验公式,即线程池规模 = CPU * 2 + 1,现在机器基本都是双核或多CPU的。当用户建立5个以上的下载任务时,迅雷最多同时执行5个下载任务,超出的任务将排队等待。一旦有下载任务完成,另一个等待下载任务即启动。迅雷对于下载线程数的限制,即使用了信号量机制。


4)互斥量(Mutex)
互斥是一种用途非常广泛的内核对象。能够保证多个线程对同一共享资源的互斥访问。痛临界区有些类似,只有拥有互斥对象的线程才具有访问资源的权限。由于互斥对象只有一个,因此就决定了任何情况下,此共享资源都不会被多个线程所访问。当前占据资源的线程在任务处理完后应该将占据的互斥对象交出,以便其他线程在其上的等待调用WaitForSingleObject/WaitForMultipleObjects返回。

基于互斥内核对象来保持线程同步用到函数主要有CreateMutexOpenMutexReleaseMutex等函数:

// The CreateMutex function creates or opens a named or unnamed mutex object.
HANDLE CreateMutex(
                 LPSECURITY_ATTRIBUTES lpMutexAttributes,  // SD
                 BOOL bInitialOwner,                // initial owner
                 LPCTSTR lpName                      // object name
                 );

参数bInitialOwner主要用来控制互斥对象的初始状态,一般将其设为FALSE,以表明互斥对象在创建时并没有为任何线程所占有。最后一个参数即内核对象名称,以便跨进程执行OpenMutex按名访问。

当目前对资源具有访问权限的线程不再需要访问此资源而要离开时,必须通过ReleaseMutex函数来释放其拥有的互斥对象。

// The ReleaseMutex function releases ownership of the specified mutex object.
BOOL ReleaseMutex(
                HANDLE hMutex   // handle to mutex
                );

基于互斥内核对象的同步在代码上体现在对WaitForSingleObject/WaitForMultipleObjects函数的调用,以等待互斥内核对象的通知,其同步条件为某一时刻只有一个线程拥有互斥对象。

在互斥对象通知引起调用等待函数返回时,等待函数的返回值不在是WAIT_OBJECT_0[WAIT_OBJECT_0WAIT_OBJECT_0+nCount-1]之间的某值,而是将返回WAIT_ABANDONED_0或是[WAIT_ABANDONED_0WAIT_ABANDONED_0+nCount-1之间的某值,以此来表明线程正在等待的互斥对象由另外一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此之外,使用互斥内核对象的方法在等待线程的可调度性上同使用其他几种内核对象的方法也有所不同,其他内核对象在没有得到通知时,受调用等待函数的作用,线程将会挂起,同时丧失可调度性,而使用互斥的方法可以在等待的同时仍具有调度性,这也正是互斥对象所能完成的非常规操作之一

在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,可以确保任何线程在处理此内存快时,都对其拥有可靠的独占访问权。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值