第九章 多线程编程
前言:
pthread_create线程创建后运行其对应函数,运行完后就退出了,另一种主动退出为调用pthread_exit。
由于一个进程中的多个线程是共享数据段,因此,线程退出后其所占用的资源不会释放,要由进程来统一释放。那么,进程释放资源时,必须保证所有线程已经退出,因此,会在进程调用exit(0)之前,调用pthread_join(线程标识符),等待线程结束(阻塞)。
=====================================================================
9.1 Linux下线程概述
9.1.1 线程概述
进程是系统中程序执行和资源分配的基本单位。每个进程都拥有自己的数据段、代码段和堆棧段。
线程,是一个进程内的基本调度单位,也可以称为轻量级进程。
线程是在共享内存空间中并发的多道执行路径,它们共享一个进程的资源。
一个进程可以有多个线程,也就是有多个线程控制表及堆栈寄存器,但却共享一个用户地址空间。由于共享,多线程中的同步就是非常重要的问题。
9.1.2 线程分类
线程按照其调度者可以分为用户级线程和核心级线程两种
(1)用户级线程
用户级线程主要解决的是上下文切换的问题。
如果一个进程中的某一个线程调用了一个阻塞的系统调用,那么该进程包括该进程中的其他所有线程也同时被阻塞。主要缺点是在一个进程中的多个线程的调度无法发挥多处理器的优势。
(2)核心级线程
这种线程允许不同进程中的线程按照同一相对优先调度方法进行调度,这样就可以发挥多处理器的并发优势。
一个用户级线程可以对应一个或几个核心级线程,也就是“一对一”或“多对一”模型。
9.1.3 Linux线程技术的发展
在Linux2.2内核中,线程是通过fork创建的“轻”进程,并且线程的个数也很有限,最多只能有4096个进程/线程同时运行。
在Linux2.4内核中,一个用户级线程对应一个内核线程
在Linux2.6内核中,仍然采用一对一线程模型
9.2 Linux线程实现
9.2.1 线程基本操作
用户空间线程的操作。pthread线程库。
1. 线程创建和退出
(1)函数说明
O创建线程就是确定该线程函数的入口点 pthread_create 该函数运行完之后,该线程就退出了,这也是线程退出的一种方法
另一种退出线程好几的方法是函数pthread_exit,这是线程的主动行为。
O在使用线程函数时,不能随意使用exit退出函数进行出错处理,由于exit的作用是使调用进程终止,往往一个进程包含多个线程,因此,在使用exit之后,该进程中的所有线程都终止了。
因此,在线程中就可以使用pthread_exit来代替进程中的exit
O由于一个进程中的多个线程是共享数据段的,退出线程所占用的资源并不会随着线程的终止而得到释放。正如进程之间可以用wait()系统调用来同步终止并释放资源一样,线程之间也有类似机制,那就是pthread_join()函数。pthread_join可以用于将当前线程挂起,等待线程的结束。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源就被收回。
(2)函数格式
(3)函数使用
#include<stdio.h> #include<pthread.h> void thread1(void){ int i=0; for(i=0; i<6; i++){ printf("This is a pthread1.\n"); if(i==2) pthread_exit(0); sleep(1); } } void thread2(void){ int i = 0; for(i=0;i<3;i++) printf("This is a pthread2.\n"); pthread_exit(0); } void main(void){ pthread_t id1, id2; int i, ret; ret = pthread_create(&id1, NULL, (void *)thread1, NULL); if(ret != 0){ printf("Create pthread error!\n"); exit(1); } ret = pthread_create(&id2, NULL, (void *)thread2, NULL); if(ret != 0){ printf("Create pthread error!\n"); exit(1); } pthread_join(id1, NULL); pthread_join(id2, NULL); exit(0); }
编译命令:gcc thread.c -o othread -lpthread
2. 修改线程属性
(1)函数说明
pthread_create函数的第二个参数,线程的属性。
属性包括绑定属性、分离属性、堆栈地址、堆栈大小、优先级。系统默认属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。
*绑定属性
Linux中采用“一对一”的线程机制,也就是一个用户线程对应一个内核线程。绑定属性就是指一个用户线程固定地分配给一个内核线程,因为CPU时间片的调度是面向内核线程(也就是轻量级进程)的,因此具有绑定属性的线程可以保证在需要的时候总有一个内核线程与之对应。而与之相对的非绑定属性就是指用户线程和内核线程的关系不是始终固定的,而是由系统来控制分配的。
*分离属性
分离属性是用来决定一个线程以什么样的方式来终止自己。
在非分离情况下,当一个线程结束时,它所占用的系统资源并没有被释放,也就是没有真正终止。只有当pthread_join()函数返回时,创建的线程才能释放自己占用的系统资源。
在分离属性情况下,一个线程结束时立即释放它所占有的系统资源。
这里要注意的一点是,如果设置一个线程的分离属性,而这个线程运行又非常快,那么它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这时调用pthread_create的线程就得到了错误的线程号。
(2)函数格式
线程属性初始化:
int pthread_attr_init(pthread_attr_t * attr);
设定绑定属性:
int pthread_attr_setscope(pthread_attr_t *attr, int scope)
scope: PTHREAD_SCOPE_SYSTEM 绑定| PTHREAD_SCOPE_PROCESS 非绑定
设定分离属性:
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)
detachstate: PTHREAD_CREATE_DETACHED 分离| PTHREAD_CREATE_JOINABLE 非分离
获取和设置线程优先级:
int pthread_attr_getschedparam(pthread_attr_t *attr, struct sched_param *param)
int pthread_attr_setschedparam(pthread_attr_t *attr, struct sched_param *param)
3. 使用实例
9.2.2 线程访问控制
由于线程共享进程的资源和地址空间,因此在对这些资源进行操作时,必须考虑到线程间资源访问的惟一性问题。互斥锁和信号量-线程同步的方法
1、mutex互斥锁线程控制
(1)函数说明
若其他线程希望上锁一个已经上锁了的互斥锁,则该线程就会挂起,直到上锁的线程释放掉互斥锁为止。
pthread_mutex_init / pthread_mutex_lock / pthread_mutex_trylock / pthread_mutex_unlock / pthread_mutex_destroy
其中,互斥锁可以分为快速互斥锁、递归互斥锁和检错互斥锁。
(2)函数格式
互斥锁初始化:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutextattr_t *mutextattr)
mutexattr:
PTHREAD_MUTEX_INITIALIZER 创建快速互斥锁|
PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP 创建递归互斥锁|
PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP 创建检错互斥锁
互斥锁上锁:
int pthread_mutex_lock(pthread_mutex_t *mutex,)
互斥锁判断上锁:
int pthread_mutex_trylock(pthread_mutex_t *mutex,)
互斥锁解锁:
int pthread_mutex_unlock(pthread_mutex_t *mutex,)
消除互斥锁:
int pthread_mutex_destroy(pthread_mutex_t *mutex,)
(3)使用实例
2、信号量线程控制