一、线程:
1. 定义:是进程中的一条执行序列(执行流)。而且一个进程至少有一个线程,我们将它称为主线程(main线程)
- main函数的执行过程,可以通过线程库创建其他的多条线程。
- 函数线程:从指定的函数入口开始运行,到此函数结束停止。
2.进程与线程的区别
- 线程是进程内部的执行序列,一个进程至少包含一条线程,线程必须依赖于进程;
- 进程与进程之间相互独立的,而进程中的线程共享进程内的资源(.data .bass .heap PCB);
- 进程是系统内部资源分配的单位,线程是系统调度执行的单位;
- 进程之间需要独特的技术进行通信,而进程中的线程通讯只需将传递的数据保存到全局区或堆区;
3.线程实现方式
- 用户级线程:
线程的创建、销毁、管理都在用户空间完成,内核只会识别一个进程,一条线程。
优点:灵活(操作系统不知道线程存在,可在任何平台使用);切换效率高(不需要切入内核);实现简单、无需修改操 作系统;
缺点:编程复杂,用户必须自己管理线程,包括线程调度;如果线程阻塞,整个进程都会阻塞;不能使用对称多处理器。
- 内核级线程 :
线程的创建、销毁、管理由操作系统内核完成。
优缺点:使用户编程简单、但每次切换都得陷入内核,效率低。
- 混合级线程:
线程中的一部分由用户级线程创建,一部分由内核线程创建,是一个多对多的关系。结合了用户级和内核级的优点。
4.线程创建
头文件:#include<pthread.h> 库文件:libpthread.so
- 创建:
int pthread_create(pthreat_t *id,pthread_attr *attr,void*(*pthread_fun)(void *fun)fun,void *arg);//id,获取线程id; *attr, 线程的属性(默认为NULL);arg给新线程指定的函数传递的参数;fun指定线程的执行序列。
说明:pthread_create创建一条函数线程,线程从pthread_fun函数入口地址开始执行,到pthread_fun函数结束。成功返回0 ,失败返回错误码。
同一个进程的线程之间共享全局(.data .bss)、堆区的数据。而且在同一进程中,任意打开的文件资源都是所有线程共享 的。
- 线程传参:值传递,最大4个字节;地址传递,在函数线程修改传递的地址空间上的值,主线程中的值也随之改变。
- 线程结束函数
int pthread_exit(void *result);// result,指定传递的结束信息;仅仅直接结束调用此函数的进程,如果进程中还有线程运 行,那么该函数会等所有线程结束后结束该进程。
- 等待线程结束函数——>进程中的wait函数
int pthread_join(pthread_tid,void **result);//调用此函数的线程会被阻塞,直到等待的线程结束
- 终止线程:
int pthread_cancel(pthread_t id);
由于线程是进程中的一条执行序列,所有一个进程中的所有线程共享全局、堆区数据,包括打开的文件描述符。只有每个线程栈区的数据是线程独享的。
二、线程同步——>信号量 互斥锁 条件变量 读写锁
1. 信号量
1)定义:和进程的信号量作用相似,当线程访问一些有限的共享资源时,就必须做到线程同步访问。类似一个计数器,有一个初始值用于记录临界资源的个数。
2)使用方法:
- 头文件:#include<semaphore.h>
- 初始化:int sem_init(sem_t *sem,int shared,int val);//信号量一般被定义为在线程共享的全局数据区域。
sem_init函数是将信号量sem的初始值设置为val;shared参数控制这个信号量是否可以在多个进程之间共享,一般设置 为 0。但Linux表示不支持。
- PV操作:
P操作:int sem_wait(sem_t *sem)//对sem进行-1操作,若小于0,把此函数调用会阻塞,直到有其他线程执行V操作
V操作:int sem_post(sem_t *sem)//对信号量进行+1操作
- 销毁信号量:
int sem_destroy(sem_t *sem);
2.互斥锁
1)介绍:互斥锁只能在线程之间使用的一种控制临界资源访问的机制。如果一个线程要访问临界资源,则必须先加锁,用完之后解锁,这样在一个线程访问临界资源的过程中,其他线程加锁会阻塞,不能访问临界资源的临界区,直到访问临界资源的线程用完后解锁。
2)使用方法:
- 头文件:#include<pthread.h>
- 初始化:int pthread_mutex_init(pthread_mutex_t *mutex,pthread_mutexattr_t *attr);//互斥锁一般被定义在线程共享的全局数据区,此函数是初始化互斥锁mutex,attr为锁的属性。解锁状态
- 加锁:int pthread_mutex_trylock(pthread_mutex_t *mutex)或int pthread_mutex_lock(pthread_mutex_t *mutex);//trylock会直接返回不会阻塞,lock表示在加锁状态,会阻塞。
- 解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);
- 销毁锁:int pthread_mutex_destory(pthread_mutex_t *mutex);
3.条件变量:提供了一种信号通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程。
三、线程安全
描述:
每一个进程都是独立的个体,进程间不会相互影响。
因为线程之间共享全局数据,静态数据,在编程过程中,必须对线程访问的共享资源做同步控制,但有些系统调用或库函数实 现是,用到了静态数据。当在多线程环境中调用这些函数时,就会出现不安全现象。对于这些函数、在多线程环境中使用它们的安全体,即可重入版本。
解决方法:
- 对线程同步,保证同一时刻只有一个线程访问临界资源。
- 在多线程中使用线程安全的函数(可重入函数)。线程安全函数指的是如果一个函数能被多个线程同时调用不会发生竞争状态,我们称它时线程安全的。
同一份代码,相同的初始值,执行次数不同,结果也不相同。 执行结果存在二义性。
线程fork使用即锁的继承:
在多线程中,某一条线程调用fork之后生产子进程,在子进程中,只有调用fork函数的线程会被启动,其他线程不会运行。
子进程会继承父进程的锁及其锁的状态,所以子进程可能会发生死锁!!!!
解决方案:在fork之前调用pthread_atfork函数。
附加:
- 线程同步:同一个进程中的线程都是并发执行的,如果两个以上的线程需要协调工作,就需要线程同步。
- 竞争关系:访问临界资源时;
- 协作关系:一个线程为另一个线程提供服务——>生产者消费者模式
- 生产者与消费者:
描述:主线程获取用户输入数据,函数线程将用户输入的数据全部转换为大写,并存储在文件.txt中。
解决方法:
- 主线程与用户交互,获取数据;
- 主线程需要将获取的数据传递给函数线程——>全局、堆区、栈区(传参时,传递栈区空间大小的地址)
- 函数线程将获取的数据转换为大写,存储在文件中。
问题:如何解决消费者的出了在生产者之后????