#1 ============== 进程管理 ==============

(API)创建一个进程:CreateProcess

Windows下不会维护父-子进程的关系。不像unix中那样会牵扯到会话首进程、僵尸进程之类的。

(!!!)对线程句柄的关闭不会终止线程,因此在创建完线程后可以直接关闭句柄。

在创建进程时可以指定一个是否继承句柄的参数,如果true,则会集成父进程所有已经打开
的句柄的副本,比如文件、内存映射等等。

unix下的fork不适用与多线程场景。

Windows下CreateProcess 约等于  Unix下的 fork + execl

上面提到了子进程可以选择集成父进程的所有打开句柄,这是有前提的,那就是句柄创建时的
内核对象安全属性中包含了可集成属性。

(API)获得当前进程句柄:GetCurrentProcess
(API)获得当前进程ID:GetCurrentProcessId

注:Windows环境下的HANDLE是操作系统层面唯一的,因此存在句柄泄漏的情况,但是对于同一个文件
    很多进程都会打开它,每个进程都会有一个文件HANDLE,这个HANDLE 和 文件就不是 一一对应的
    关系,而是一对多。
    此外,子进程从父进程集成而来的句柄是原句柄的“副本”,是有自己的HANDLE编号的,不会和原来
    父进程句柄的HANDLE编号重复。
    
(API)复制句柄:DuplicateHandle            //句柄编号不一样,只是句柄代表的资源是一样的

(API)进程退出:ExitProcess
(API)获取进程退出码:GetExitCodeProcess
(API)在一个进程中终止另一个进程:TerminateProcess             //要求被中的进程在创建是指定了PROCESS_TERMINATE安全访问权限

如果在__try中调用ExitProcess,那么__except和__finally都不会被执行。

(API)等待进程终止(windows下的所有资源都能用它来设置阻塞):WaitForSingleObject                //传入进程句柄即可
(API)等待多个进程终止:WaitForMultipleObjects
(API)获取环境变量:GetEnvironmentVariable
(API)设置环境变量:SetEnvironmentVariable

CreateProcess时指定PROCESS_TERMINATE,可以控制当前进程不会被其他进程通过TerminateProcess终止

(API)获取进程执行时间:GetProcessTimes        //通过参数返回数据,可以获得进程消耗的所有时间,包括内核态和用户态
(API)获取线程消耗时间:GetThreadTimes

#2 ============== 线程 和 调度 ==============

(API)挂起线程:SuspendThread
(API)恢复线程:ResumeThread

所有使用全局变量 和 堆的代码段都不是线程安全的。

C库不是线程安全的,比如strtok会使用全局变量,因此在多线程场景下使用C库存在一定的风险。像printf,strcpy等等这些都是标准C库,
具体可参照 c99 等一系列C库标准。
Windows下提供 LIBCMT.LIB 这么一个库来作为非线程安全的C库的替代品,LIBC 代表是 C库的等价库,MT代表 Mutithread库,
对应编译选项的 /MT 和 /MD (虽然名字里有MT,但是/MT 和 /MD 都是使用这个名字)

如果使用了 LIBCMT.LIB ,那么就不要在使用CreateThread来创建线程了,这会引入风险,请使用_beginthread和_beginthreadex。
同时线程的终止使用 _endthreadex替代ExitThread。
最终,建议使用 _beginthreadex创建线程,使用_endthreadex结束线程。或者使用return结束线程也可以

(API)创建线程:_beginthreadex
(API)线程自行退出:_endthreadex

(!!!)通常情况下,我们都使用/MD选项,而不用/MT,因此选项要求所有此二进制文件依赖的其他二进制文件在编译时具备相同的配置,
因此大家有一个心照不宣的约定,就是都使用/MD,而不使用/MT,因为某些第三方库的发布如果把c库整个链接仅自己的DLL会把自己
的DLL搞的太大。

(API)设置进程优先级:SetPriorityClass
(API)获取进程优先级:GetPriorityClass

进程可以更改自己的优先级,如果安全权限足够,进程甚至可以修改其他进程的优先级

        PS:windows系统中内核对象的安全权限是一个很重要的部分,后面会详细讨论,所有内核对象都具备安全权限

线程的优先级默认和所属线程一致,可以通过相应的函数设置,设置的方式可以是绝对的或者相对所属进程加/减的。

(API)设置线程优先级:SetThreadPriority
(API)获取线程优先级:GetThreadPriority

线程的优先级被钳定到进程上以后,如果进程优先级改变,则线程优先级也会相应地跟着变化。

(API)开/关进程线程优先级绑定关系:SetThreadPrioirtyBoost

#3 ============== 线程同步 ==============

(API)原子加:InterlockedIncrement
(API)原子减:InterlockedDecrement

CRITICAL_SECTION 每次只会放一个等待中的线程进入临界代码区

(API)设置带自旋锁的临界区的计数值:SetCriticalSectionSpinCount
(API)初始化带自旋锁的临界区:InitializeCriticalSectionAndSpinCount
    注:带自旋锁的临界区时用来在特定场景下使用的,当无法进入临界区时,会自动进入自旋,当自旋到一定
        计数后依旧没法进入则会退出,这边增加了安全性,防止所有线程都挂死。

(API)跨进程打开一个互斥量:OpenMutex            //多进程场景下使用,这个函数会要求指定Mutex的名称

Mutex 和 CRITICAL_SECTION 对比:
1)如果持有Mutex的线程没释放对Mutex的锁就退出了,那么Mutex的HANDLE将变为激发态,其他线程再去加锁
    Mutex会被告知 持有HANDLE的线程已经终止。
2)Mutex可通过 WaitForSingleObject 进行超时等待,CRITICAL_SECTION只能通过tryenter,然后检查返回值并轮询进行超时机制的封装。
3)Mutex可以命名,因此可以跨进程使用,OpenMutex 可以用来打开别的进程已经打开的Mutex。CS不能跨进程使用。
    注:Windows下单凡可以命名的内核对象都可以跨进程使用
4)CS比Mutex快100倍

HeapLock  和 HeapUnlock用来保护 具有HANDLE的堆,如果一个线程通过HeapLock锁住了一个堆,那么除非他HeapUnlock,
否则其他线程无法在堆中分配和释放内存(使用堆HANDLE)
如果制定了 HEAP_NO_SERIALIZE 标志,那么无法使用这两个函数。

(API)创建信号量:CreateSemaphore / CreateSemaphoreEx
(API)打开信号量:OpenSemaphore
(API)释放信号量:ReleaseSemaphore

说明:1)创建信号量用来为信号量创建一个初始计数,并设置它的最大值能到多少
      2)使用WaitForSingleObject等待信号量,会尝试使计数减一,如果此时计数为0,则线程会被阻塞。如果计数不为0,则计数减一,且不被阻塞
      3)使用ReleaseSemaphore可以使技术减少任意指定的值,最多可以减少信号量的最大值(最大值在创建信号量时指定),一般情况下都是逐个递减
      
      
二元信号量和互斥量还是有一定的差别的,因此能用互斥量的地方不要使用二元信号量。

虽然信号量很方便,但是 使用互斥量 和 事件更加方便,且功能也更强大,大部分情况下都不使用信号量。

连续两次释放信号量可能会导致死锁,目前没有完全可靠的方案保证连续两次对信号量WaitForSingleObject可以百分百安全。

(!!!)Windows环境下,线程间同步机制推荐使用:CS / Mutex / 事件,这三者可以解决所有同步问题。

    +++ 事件 +++
    
Windows下最重要也是最全能的线程间同步机制就是事件,事件的一个重要能力有:
1)当一个事件变为激发态后,多个线程可以从等待状态 “同时” 释放,继而跳出阻塞。
2)事件分为手动复位和自动复位两种,在CreateEvent的时候可以设置。
3)手动复位的事件可以传信给许多同时等待事件的线程,然后由编程者手动进行复位,或者根据就不复位,相当于事件就触发一次。
4)自动复位的事件传信给单个等待事件的线程,该事件会 “自动” 复位。

    如果一个事件选择广播,那么它只能手动复位
    如果一个事件选择单播,那么它可以选择手动复位,或者自动复位。
    
    
(API)创建事件:CreateEvent / CreateEventEx
(API)打开事件:OpenEvent
(API)打开事件:SetEvent
(API)关闭事件:ResetEvent
(API)触发单次事件(给所有等待线程传一次信,然后关闭事件):PulseEvent
(API)等待事件:WaitForSingleObject WaitForMultipleObjects

SetEvent的详细描述:
如果事件是自动复位的,那么只有一个等待事件的线程被解除阻塞,事件接着会被设置为非激发态。如果此时并没有等待中的线程,
那么事件会被排队,知道某一个线程消化掉这个事件,然后事件会被设置为非激发态。
如果事件是手动复位的,那么这个事件将一直处于激发态直到被某个线程使用ResetEvent将事件重置为非激发态,而这期间所有
通过WaitForSingleObject / WaitForMultipleObjects 等待此事件的线程都将畅行无阻!!!

PulseEvent的详细描述:
如果是手动复位事件,此动作会触发一个脉冲,给所有的等待中线程,每一个线程一个脉冲,帮助他们解除阻塞,然后将事件重置为非激发态。
如果是自动复位事件,此动作只会随机挑选一个线程,让他解除阻塞,然后将事件设置为非激发态。
    
    
Windows下 事件的行为 和 Linux下 条件变量的行为非常相似。
Windows下NT6中已经实现了条件变量。

事件使用场景总结:

SetEvent + 自动复位事件:只有一个线程会被释放,且事件会被排队。
SetEvent + 手动复位事件:所有等待事件的线程都会被释放,且一直处于畅通无阻状态,直至某个线程通过Reset将事件关闭。
PulsEvent + 自动复位事件:只有一个线程会被释放,但是事件不会被排队,如果当前没有线程正在等待,那么这个事件将被丢弃。
PulseEvent + 手动复位事件:所有等待事件的线程都会被释放一次,同时事件也不会被排队,如果某个线程在此时没有处于等待状态,那么它将只能等待下一次脉冲。

(!!!)对于SetEvent 和 PulseEvent 可以理解为 高电平触发事件,SetEvent相当于将电平设置为高电平,PulseEvent相当于产生一个脉冲电平。

#4 ============== 锁、性能及NT6增强 ==============

CRITICAL_SECTION快于Mutex的原因在于其 加解锁均在用户态进行,不需要切换到内核态,因此不需要进行上下文切换和系统调用。

初始化CRITICAL_SECTION时可以为其指定自旋锁,同时使用InitialCriticalSectionAndSpinCount代替InitialCriticalSection进行初始化。
CS的自旋锁仅仅在多处理场景下生效,单处理器不生效。
    PS:自旋锁计数也可以在

自旋锁的行为是:当CS 使用EnterCriticalSection 和 TryEnterCriticalSection进行 加锁时,如果此时此刻不可加锁,那么会重复循环
                自旋锁计数的次数,如果依旧没有成功则会退出EnterCriticalSection动作而返回。一般情况下自旋锁计数设置为4000,如果
                临界区代码很短小,且处理器较多,那么可以适当缩小这个值。

线程堆栈的默认大小是1MB,那么1000个线程将常占1GB的虚拟内存空间。
线程上下文切换非常耗时。
线程上下文切换可在堆栈访问期间产生页错误。

#5 ============== 高级线程同步 ==============

#6 ============== 进程间通讯 ==============

#7 ============== 使用Windows套接字 ==============