在多任务操作系统环境下,进程和线程的同步控制是多线程和多进程编程的一个重点,稍作总结
  一、临界区(criticalsection)
  1、临界区是线程同步的一种方式,即它在同一时刻只允许一个线程进入,其他线程只能等往此临界区被释放后才能进入,否则只能等待,线程也将挂起。需要注意的是临界区在同一线程中可以重复连续的多次进入,它并不像互斥型信号量一样只能进入一次。但进入多少次在线程不需要使用临界区的时候,便需要释放多少次,即enter和leave的个数要相等。否则的话,会阻挠其他线程的进入。如果一个线进入临界区而没有leave之前down掉了,那个其他要进入临界区的线程只能死等了,而且因为临界区不是内核对象,所以系统没有办法清除临界区。而且不会知道进入临界区的线程是生是死,这也是临界区的一个缺陷。在编程时解决此缺陷的一个方法是封装一个类,以临界区引用作为参数,在构造时进入临界区,在析构时离开临界区。这样每次在使用临界资源时,只需要构造此类的临时对象,而不用再想着在用完临界区时再离开,因为临时变量在销毁时会调用析构函数,从而离开临界区。
  2、使用临界区也可能会引起死锁,如果一段代码需要使用两个以上的临界资源,而这段代码又在不同的线程中出现,那么会出现引起死锁的可能,因为两个线程在进入资源时可能顺序不一样,从而出现互等现象。虽然有一些方法可以侦测死锁,但一般都过于复杂,所以好是在编程时找到一种方法确保死锁不会发生。
  像倔强的哲学家一样,他们的进餐不会谦让,所以他们需要付出代价,那是死锁产生时,他们只能获得左手旁的一支筷子,而且又都不愿意松手,终结果是他们都不能进餐。
  我们解决问题要找到根源,哲学家进餐问题的产生的根源有三个,一是他们同时需要两支筷子,也是两个临界资源才能进餐。二是他们都很“倔强”,也是有比较高的自由意识,从来不愿意把左手的筷子让给别人。三是有多个人想要同时进餐。所以我们针对这三个特点可以采取三种方式来解决问题。一是让他们一次只需要一支筷子能进餐,这种方式不可取,因为一支筷子没法进餐,放到编程中也是资源少了一个无法解决问题。二是限制他们的自由意识,让他们愿意放弃自己已经到手的筷子。这时候需要提前检查能不能同时获取两支筷子,如果不能那一个都不要获取,即只能两个资源都可以同时得到时才去获取资源。这种方法是可取的,但在实际编程中,由于临界区不是内核对象,所以不能提前检查状态。即不能使用waitformultiobject。要采用这种方法要更改同步控制的资源,不能使用临界区。第三种方法是减少使用人数,让他们在同一时刻只允许有一人进餐。这种方法也是限制他们的自由意识,对编程来说不可取,因为大家(线程)可能要同时使用。
  虽然临界区有很多缺点,但在实际编程中仍然运用很广泛,这是因为死锁产生是需要条件的,在死锁条件不满足的情况下是不会产生死锁的。比如在解决问题时不需要多于一个的临界区,也是哲学家只需要一支筷子能进餐,这时候我们可以使用临界区。还有我们在编程时也可以刻意控制在同一时刻只允许一个哲学家(线程)进餐。只要破坏产生死锁的三个条件可以使用临界区了。
  3、因为临界区不是核心对象,所以它只在同一进程的不同线程中起作用,即工作在用户态,速度也非常快。
  二、互斥器(mutex)
  1、互斥器是mutual exclusion的缩写,它是操作系统核心对象,可以工作在进程间或线程间或进程和线程间,但因为要锁定时要在核心态和用户间切换,所以效率比较低。
  2、使用createmutex来创建互斥器,如果在同一进程中,可以创建没有名称的互斥器,使用句柄来进行操作,如果在不同的进程中使用,可以创建命名互斥器,如果创建的互斥器在操作系统中已经存在同名的互斥器,会返回此互斥器的句柄,这时候和使用openmutex打开此互斥器的效果相同(注意此句柄和创建时候的句柄不同,因为操作系统会给互斥器引用计数,每次使用都会增加引用计数,使用完毕后,使用closehandle关闭句柄,引用计数减1,引用计数为0时会撤消此互斥器),这时候如果使用GetLastError会返回已经存在的信息。