多线程编程
一、线程的基本概念
与进程相比,多线程是一种非常“节俭”的多任务操作方式。在linux操作系统下,启动一个新进程必须给
它分配独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种“昂贵”的多任务
工作方式;而运行一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个
线程所花费的空间远远小于启动一个进程所花费的空间,线程间彼此切换所需的时间也远远少于进程间切换
所需要的时间。
进程作为独立的实体,它为线程提供运行的资源并构成静态环境。线程是处理机调度的基本单位。如果
说进程概念很好的描述了单机操作系统行为,那么线程概念则很好的描述了多机系统的并行处理行为。
多线程工作方式提供了线程间方便的通信机制。由于同一进程下的线程之间共享数据空间,所以一个线
程的数据可以直接为其它线程使用,这不仅快捷,而且方便。
多线程程序还有以下优点:提高应用程序响应,使cpu系统更加有效,改善程序结构。
二、线程的实现
linux系统下的多线程遵循posix线程接口,称为pthread。编写linux下的多线程程序,需要使用头文件
pthread.h,连接时需要使用库文件libpthread.a.(因此编译时需要添加选项:-lpthread)
(1)创建线程:即确定调用该线程函数的入口点,通常使用的函数是pthread_create。
函数原型:
(2)线程挂起
一个进程中的多个线程是共享数据段的,因此通常在线程退出后,退出线程所占用的资源并不会随
着线程的终止而释放。就如进程中的wait(),线程之间使用pthread_join()函数将当前线程挂起,等待
线程的结束。pthread_join()是一个线程阻塞的函数,调用它的函数将一直等待到被等待线程的结束。
需要注意的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余的将
返回错误代码。
函数原型:
(3)线程的结束:1、相关线程函数运行结束;2、调用pthread_exit()函数
示例:
三、修改线程属性
线程属性值不能直接设置,需要调用相关的函数进行操作,初始化的函数为pthread_attr_init,这个函
数必须在pthread_create函数之前调用。属性对象包括是否绑定、是否分离、堆栈地址、堆栈大小和优先级。
默认的属性为非绑定、非分离、缺省1mb的堆栈和与父进程同样级别的优先级。对于大多数程序来说,使用默
认属性就够了。
1、绑定
关于绑定涉及到另一个概念:轻进程lwp(light weight process)。可理解为内核线程,系统对线程
资源的分配和对线程的控制是通过轻进程来实现的。默认情况下,由系统来控制启动多少轻进程、哪些轻进
程控制哪些线程,这种状态称为非绑定的。绑定即某个线程固定的“绑”在一个轻线程上。被绑定的线程具
有较高的反应速度(cpu时间片的调度是面向轻进程的)。
通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足实时反应之类的要求
创建一个绑定的线程:(pthread_attr_setscope)
2、分离
线程的分离状态决定一个线程以什么样的方式来终止自己。
非分离:原有的线程等待创建的线程结束,只有pthread_join()函数返回时才释放所占资源。
分离:不被其他进程所等待,运行结束后马上释放所占资源。
线程分离状态的函数:
如果设置了一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数
返回之前就终止了,这样调用pthread_create的线程就可能得到错误的线程号。为避免这种情况,
通常在创建的线程里调用pthread_cond_timewait函数,让这个线程等待会儿。
3、优先级
优先级存放在结构sched_param中,用函数pthread_attr_getschedparam和pthread_attr_setschedparam
进行存放。一般而言是先取优先级,然后对优先级进行修改后再存放回去。
例如:
四、多线程访问控制
由于多线程共享进程的资源和地址空间,因此对这些资源进行操作时,必须考虑到线程间资源访问的唯
一性问题。
互斥锁有一个明显的缺点:只有两种状态(锁定和非锁定),而条件变量通过允许线程阻塞和等待另一个线
程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。
1、pthread_cond_init ()函数 —— 用于初始化一个条件变量
int pthread_cond_init(pthread_cond_t * cond, __const pthread_condattr_t * cond_attr)
cond: 指向结构pthread_cond_t的指针
cond_attr: 条件变量的属性结构(默认值——pthread_process_private)
初始化条件变量只有未被使用时才能重新初始化或被释放。
释放条件变量的函数:pthread_cond_destroy(pthread_cond_t cond)
2、pthread_cond_wait()函数 —— 使线程阻塞在一个条件变量上
extern int pthread_cond_wait(pthread_cond_t * __restrict__cond,
pthread_mutex_t * __restrict__mutex)
线程解开mutex指向的锁并被条件变量cond阻塞。
3、pthread_cond_timedwait()函数 —— 用于阻塞线程的另一个函数
extern int pthread_cond_timedwait __p((pthread_cond_t * __restrict__cond,
pthread_mutex_t * __restrict__mutex, __const struct timespec * __abstime))
它比thread_cond_wait()函数多了一个时间参数,经历abstime时间后,即使条件变量不满足,
阻塞也被解除。
4、pthread_cond_signal()函数 —— 用来释放被阻塞在条件变量cond上的一个线程。
extern int pthread_cond_signal(pthread_cond_t * __cond)
需要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和
调用pthread_cond_wait()函数之间被发出,从而造成无限制的等待。
5、其它常用线程函数
获得父进程id:
pthread pthread_self(void)
测试两个线程号是否相同:
int pthread_equal(pthread_t __thread, pthread_t __thread2)
互斥量初始化:
int pthread_mutex_init(pthread_mutex_t * , __const pthread_mutexattr_t * )
销毁互斥量:
int pthread_mutex_destroy(pthread_mutex_t *__mutex)
再试一次获得对互斥锁量的锁定(非阻塞)
int pthread_mutex_trylock(pthread_mutex_t *__mutex)
锁定互斥量(阻塞)
int pthread_mutex_lock(pthread_mutex_t *__mutex)
解锁互斥量
int pthead_mutex_unlock(pthread_mutex_t *__mutex)
条件变量初始化
int pthread_cond_init(pthread_cond_t *__restrict__cond,
__const pthread_condattr_t *__restrict__cond_attr)
销毁条件变量cond
int pthread_cond_destroy(pthread_cond_t *__cond)
唤醒线程等待条件变量
int pthread_cond_signal(pthread_cond_t *__cond)
等待条件变量
int pthread_cond_wait(pthread_cond_t *__restrict__cond,
pthread_mutex_t * __restrict__mutex)
在指定的时间到达前等待条件变量
int pthread_cond_timewait(pthread_cond_t *__restrict__cond,
phread_mutex_t *__restrict__mutex, __const struct timespec *__restrict __abstime)