多线程编程
模块一:线程的基本概念
- 线程的概念
进程是在进程地址空间运行的执行流
2.线程与进程的区别
- 线程是系统分配资源的实体,是系统分配资源的基本单位;线程是基本单位;
- 在linux下,没有真正意义上的线程,只有进程;
- 线程是在进程地址空间内运行的执行流;
- 线程id(用函数pthread_self()得到)
- 上下文信息,包括各种寄存器的值、程序计数器和栈指针
- 栈空间
- error变量
- 信号屏蔽字
调度优先级
3.vfork函数创建子进程的特点(两点):
vfork函数创建新进程,而该新进程的目的是exec一个新程序,它不会将父进程的地址空间拷贝给子进程,在该新进程调用exit或exec之前,它在父进程的地址空间类运行
- vfork创建的子进程保证子进程先运行,父进程再运行
4.线程库函数是由POSIX标准定义,成为POSIX thread或pthread,在linux上线程函数位于libpthread共享库中,因此在编译时要加上-lpthread
模块二:线程的控制
线程的控制包括线程的创建、等待和终止
一、线程的创建
函数:
- 参数一pthread_t * pthread:是一个输入型参数,先定义一个pthread_t id,将&id传入
- 参数二const pthread_attr_t *arr:arr是一个结构体指针,用来设置新线程的属性,一般为NULL(默认其原有属性)
- 参数三void* (start routine)(void):它是一个函数指针,执行进程分配的函数
返回值:成功返回0,失败返回出错码(因为是错误码而不是错误信息,所以不能用perror函数)
二、线程等待
函数:
- pthread_t thread:线程id
- void** retval:该参数是一个void**型的变量,是一个地址变量,表示pthead-run函数的返回值的地址,若不关心可以设置为NULL,若想输出pthread_run函数的退出码,则定义一个变量void* ret,该参数为&ret,是一个输出型参数,打印退出码即(int)ret;
返回值:成功返回0,失败返回错误码
调用pthread_join得到pthread_run的返回值,得到子进程的退出状态
thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,如下:
- pthread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值,例return((void*)123)
- 如果thread线程是被别的线程调用pthread_cancel异常取消掉,value_ptr所指向单元里存放的是常数PTHRAD_CANCELED(这是一个宏,(void*)-1)
如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数,如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数,例如:pthread_exit((void*)123);
进程的取消
函数:
尽量不要让线程自己取消自己
将错误码转换为错误信息
函数:
参数errnum:表示错误码
代码验证
部分错误码显示
验证线程pthread的三种退出方式的退出码
模块三:有关分离线程
1.线程的属性
线程是可分离和可结合的
- 一般我们在创建线程时,默认线程属性是可结合的,每个可结合的线程都需要调用pthread_join显示回收,否则会造成内存泄漏;
- 线程的属性可以设置为可分离的,不需要pthread_join显示回收,操作系统自动回收,该线程不能被其他线程杀死(在该线程调用pthread_detach函数设置为可分离的以后,不能在主线程中对该线程pthread_join,否则会出错)
- 如果一个可结合的线程结束运行但没有被pthread_join,则该线程的状态类似于进程的僵死状态
函数:
由于调用pthread_join后(等待子线程运行完毕),如果该线程没有结束,调用者会被阻塞。有些时候主线程并不希望调用pthread_join而阻塞,还要处理之后到来的请求,可在子线程调用pthread_detach(pthread_self())函数取消该线程或则在主线程中调用pthread_detach(id),取消该线程
模块四:线程的互斥与同步
由于线程间共享进程的资源,并且没有任何的保护机制,所以线程在访问临界资源时,由于没有任何的保护机制,所以可能线程在访问临街资源时发生发生线程间的切换,导致出错
例如:在对进程内的一个全局变量++,对于操作系统看来,这个算式共分为三步:
- 将全局变量从存储器读到CPU中
- 对全局变量做++
将数据写回到存储器
在这个过程中,各个线程有可能在任意时刻发生切换,发生错误
如下,都全局变量gcount累加50000次
结果图:
与我们猜想不同的原因:可能在这过程每次A线程执行完一次或多次累加才执行线程B的累加
为了增加出错率,我们要增加进程的切换,什么情况下最容易产生线程的切换?
当执行流由内核态返回到用户态时容产生进程的切换
内核态:执行操作系统的代码
用户态:执行用户自己的代码
并且我们再定义一个全局变量,将 gcount赋值给它,让累加一次不再是三步而是多步,增加切换的几率
验证代码如下:
结果图:
不是100000次,验证了我们的想法
接下来,我们引入互斥锁的概念来解决这个问题
三个函数分别是“加锁”“尝试加锁”“解锁”
使用如下图:
结果图:
当我们多创建几个线程,则交互次数多,发生错误的几率也就越大,如下:
结果图: