线程的概念:
- 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
- 线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.
- 线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU。
多线程的概念
- 多线程是指从软件或者硬件上实现多个线程并发执行的技术.
- 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。
- 最简单的比喻多线程就像火车的每一节车厢,而进程则是火车。车厢离开火车是无法跑动的,同理火车也不可能只有一节车厢。多线程的出现就是为了提高效率。
如果你的应用程序需要采取以下的操作,那么你尽可在编程的时候考虑多线程机制:
- 连续的操作,需要花费忍无可忍的过长时间才可能完成
- 并行计算
- 为了等待网络、文件系统、用户或其他I/O响应而耗费大量的执行时间
- 所以说,在动手之前,先保证自己的应用程序中是否出现了以上3种情形。
为什么需要多线程(解释何时考虑使用线程)
- 从用户的角度考虑,就是为了得到更好的系统服务;从程序自身的角度考虑,就是使目标任务能够尽可能快的完成,更有效的利用系统资源。综合考虑,一般以下场合需要使用多线程:
- 程序包含复杂的计算任务时,主要是利用多线程获取更多的CPU时间(资源)。
- 处理速度较慢的外围设备.比如:打印时。再比如网络程序,涉及数据包的收发,时间因素不定。使用独立的线程处理这些任务,可使程序无需专门等待结果。
- 程序设计自身的需要.WINDOWS系统是基于消息循环的抢占式多任务系统,为使消息循环系统不至于阻塞,程序需要多个线程的来共同完成某些任务。
- 每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行
线程的优先级
- 优先级的取值为1-10(数值越高优先级越高)。
- Public final int getPriority(); 得到线程优先级的数值。
- Public final void setPriority(int newPriority);修改线程的优先级。
- 注:优先级高不代表该线程就一定先运行,只能代表该线程先运行的可能型比较大。
控制线程周期常用的方法
- Wait()释放CPU的执行权,释放锁。
- Notify()回到wait前的状态。
- Yied()让线程临时暂停。(让线程将资源释放出来)
- Join()让该线程强行加入执行。
- SetDaemon(true)设置该线程为后台线程(当前台线程结束时,后台线程一定会一起结束)。
- 注:结束线程原理就是让run方法结束,所以只要控制run的流程即可。
为什么要线程同步
- 线程间共享代码和数据可以节省系统开销,提高效率。但也同时会导致“数据访问冲突”。如何实现线程间有机交互,并确保共享资源在某时只能被一个线程访问,就是线程同步。
- 多个线程间共享的数据称为临界资源。
注意: 代码中如果没有pthread_join,主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。
多线程的同步与互斥:
方式一:锁
- 在主线程中初始化锁为解锁状态
- pthread_mutex_t mutex;
- pthread_mutex_init(&mutex, NULL);
- 在编译时初始化锁为解锁状态
- 锁初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 访问对象时的加锁操作与解锁操作
- 加锁 pthread_mutex_lock(&mutex)
- 释放锁 pthread_mutex_unlock(&mutex)
互斥锁
- 每个对象都对应一个互斥锁标记,可以保证在某一时刻只能有一个线程访问该对象。
- 互斥锁的关键字 synchronized 可以写在某个方法上(代表锁调用该方法的对象); 可以括在要锁的语句外。
- 好处:解决了线程安全的问题
- 弊端:降低了运行效率(判断锁,且不能共享信息);容易出现死锁。
死锁:
- 两个线程A,B用到同一个对象s(s为共享资源),且线程A在执行中要用到B运行后所创造条件。在这种前提下A先开始运行,进入同步块后,对象s被锁定,接着线程A因等待B运行结束而进入阻塞状态,于是B开始运行,但因无法访问对象s,线程B也进入阻塞状态,等待s被线程A解锁。最终的结果:两个线程互相等待,都无法运行。
方式二:信号量
锁有一个很明显的缺点,那就是它只有两种状态
:锁定与不锁定。
信号量本质上是一个非负数的整数计数器,它也被用来控制对公共资源的访问。当公共资源增加的时候,调用信号量增加函数sem_post()对其进行增加,当公共资源减少的时候,调用函数sem_wait()来减少信号量。其实,我们是可以把锁当作一个0-1信号量的。
它们是在/usr/include/semaphore.h
中进行定义的,信号量的数据结构为sem_t, 本质上,它是一个long型整数
相关函数
在使用semaphore之前,我们需要先引入头文件#include <semaphore.h>
- 初始化信号量:
int sem_init(sem_t *sem, int pshared, unsigned int value);
- 成功返回0,失败返回-1
- 参数
- sem:指向信号量结构的一个指针
- pshared: 不是0的时候,该信号量在进程间共享,否则只能为当前进程的所有线程们共享
- value:信号量的初始值
- 信号量减1操作,当sem=0的时候该函数会堵塞
int sem_wait(sem_t *sem);
- 成功返回0,失败返回-1
- 参数
- sem:指向信号量的一个指针
- 信号量加1操作
int sem_post(sem_t *sem);
- 参数与返回同上
- 销毁信号量
int sem_destroy(sem_t *sem);
- 参数与返回同上
信号量和锁的区别
信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的”
也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进 行操作。在有些情况下两者可以互换。
两者之间的区别:
作用域
信号量: 进程间或线程间(Linux仅线程间的无名信号量pthread semaphore)
互斥锁: 线程间
上锁时
信号量: 只要信号量的value大于0,其他线程就可以sem_wait成功,成功后信号量的value减一。若value值不大于0,则sem_wait使得线程阻塞,直到sem_post释放后value值加一,但是sem_wait返回之前还是会将此value值减一
互斥锁: 只要被锁住,其他任何线程都不可以访问被保护的资源
以下是信号灯(量)的一些概念:
信号灯与互斥锁和条件变量的主要不同在于”灯”的概念,灯亮则意味着资源可用,灯灭则意味着不可用。如果说后两中同步方式侧重于”等待”操作,即资 源不可用的话,信号灯机制则侧重于点灯,即告知资源可用;
没有等待线程的解锁或激发条件都是没有意义的,而没有等待灯亮的线程的点灯操作则有效,且能保持 灯亮状态。当然,这样的操作原语也意味着更多的开销。
信号灯的应用除了灯亮/灯灭这种二元灯以外,也可以采用大于1的灯数,以表示资源数大于1,这时可以称之为多元灯。
原子操作
在多进程(线程)访问共享资源时,能够确保所有其他的进程(线程)都不在同一时间内访问相同的资源。原子操作(atomic operation)是不需要synchronized,这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。通常所说的原子操作包括对非long和double型的primitive进行赋值,以及返回这两者之外的primitive。之所以要把它们排除在外是因为它们都比较大,而JVM的设计规范又没有要求读操作和赋值操作必须是原子操作(JVM可以试着去这么作,但并不保证)。