1. 线程的基本概念、线程的基本状态和状态之间的关系
基本概念:
-
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立的单位。
-
线程线程是轻量级进程(LWP:Light Weight Process),是程序执行流的最小单元。一个标注的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。线程是进程中的一个实体,是被CPU独立调度和分派的基本单位。线程本身不拥有系统资源,只拥有少量运行必须的资源(每个线程私有的资源只有栈和程序计数器;栈内存用来记录线程执行历史、程序计数器用来保存线程的执行位置,其他的资源如:文件句柄、全局变量、堆内存都是线程所共享的。)。
基本状态: 就绪、阻塞、和运行三种基本状态,另外还有初始态和终止态.
就绪状态: 指线程具备运行的所有条件,逻辑上可以运行,但是还没有获得处理机(CPU).
运行状态: 指线程占有处理机正在运行.
就绪状态: 指线程在等待某个事件的发生(如信号量),逻辑上不可执行。
2. 线程与进程的区别
关系: 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行。对于进程而言,线程是一个更加接近于执行体的概念。它可以和进程中的其他线程共享数据,但是用于自己的栈空间,拥有独立的执行序列。
区别: 进程和线程的主要差别在于他们是不同的操作系统资源管理方式,进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但是线程之间没有独立的地址空间,一个线程死掉就等于是整个进程死掉,所以多进程的程序要比多线程的程序要健壮,但是在进程切换时,资源消耗较大,效率更低。但是对于一些要求同时进行并且又要共享某些变量的并发操作,只能够用线程,不能够用进程。
- 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
- 线程的划分尺度小于进程,使得多线程程序的并发性高。
- 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
- 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
- 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
优缺点:
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
3. 多线程的实现方式
在JAVA中,多线程有两种实现方式,方法一:继承Thread类,重写方法run()。方法二:实现runnable接口,实现run()方法。实现同步的方法有synchronized,wait,notify
4. 多线程同步和互斥有几种实现方法?
线程间的同步大体上可以分为两类:用户模式和内核模式。内核模式是指利用系统内核对象的单一性来进行同步,使用时需要切换到内核态和用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有:原子操作(例如单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。
临界区(Critical Section同一个进程内实现互斥): 通过对多线程的串行化来访问公共资源或者是一段代码,速度快,适合控制数据访问。保证每一个时刻只有一个线程能够访问数据,若有多个线程试图访问同一个临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起。并一直持续到进入临界区的线程离开。其他要进入的线程随机抢占临界区。
互斥量(Mutex可以跨进程,实现互斥): 互斥量和临界区很相似(互斥量是内核对象),只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此在任何情况下此共享资源都不会同时被多个线程访问。互斥量比临界区复杂,因为互斥不仅仅能够在同一个应用程序的不同线程中实现资源共享,而且可以在不同的应用程序的线程之间实现对资源的安全共享。
并且互斥量是可以命名的,也就是说他可以跨越进程使用,所以要创建互斥量需要使用的资源要更多,所以如果仅仅是为了在进程内部使用的话用临界区会带来速度上的优势,并能够减少资源的占用量。
互斥量可以很好的解决由于线程意外终止资源无法释放的问题。
信号量(Semaphores,主要是实现同步,可以跨进程): 信号量允许多个线程同时使用共享资源,这与操作系统中的PV操作相同,他指出了能够同时访问资源的线程最大数目。它允许多个线程在同一个时刻访问同一个资源,但是需要限制在同一个时刻访问此资源的最大线程数。
事件(Event,实现同步,可以跨进程): 事件对象也可以通过通知操作的方式来保持线程的同步,并且可以实现不同线程中的线程同步操作。
5. 多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。
线程同步是指线程所具有的一种制约关系,一个线程的执行依赖于另外的一个线程的消息,当他们没有得到另外一个线程的消息时应该等待,直到消息到达的时候才被唤醒。
线程互斥是指对于共享的进程系统资源,在各个单元访问他的时候的排他性,当有若干个线程都要使用该资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须要等待,直到占用资源者释放该资源。
6. 对Bit field(位域)的读写操作是否是安全的?
Bit field常用来高效的存储有限数位的变量,多用于内核/底层的开发当中。一般来说,对于同一个结构体内的不同bit成员的多线程访问是无法保证线程安全的。如:
struct foo {
int flag : 1;
int counter : 15;
};
struct foo my_foo;
/* ... */
/* in thread 1 */
pthread_mutex_lock(&my_mutex_for_flag);
my_foo.flag = !my_foo.flag;
pthread_mutex_unlock(&my_mutex_for_flag);
/* in thread 2 */
pthread_mutex_lock(&my_mutex_for_counter);
++my_foo.counter;
pthread_mutex_unlock(&my_mutex_for_counter);
两个线程分别对my_foo.flag和my_foo.counter进行读写操作,但是即使有上面的加锁方式,仍然不能够保证它是线程安全的。原因在于不同的成员在内存中的具体排列方式跟Byte Order、Bit Order、对其等问题有关,不同的平台和编译器可能会排列得很不一样,要编写可移植的代码就不能假定Bit-field是按某一种固定方式排列的。而且一般来讲CPU对内存操作的最小单位是word(X86的word是16bits),而不是1bit。这就是说,如果my_foo.flag和my_foo.counter存储在同一个word里,CPU在读写任何一个bit member的时候会同时把两个值一起读进寄存器,从而造成读写冲突。这个例子正确的处理方式是用一个mutex同时保护my_foo.flag和my_foo.counter,这样才能确保读写是线程安全的。
网友评论