线程
线程是程序一次动态执行的过程,线程是一种轻量级的进程。线程可以对进程的内存空间和资源进行访问,并与
进程中的其它线程共享。
一个进程可以拥有多个线程,其中所有的线程共享该进程所拥有的资源,因此任意线程对系统资源的操作都会给
其它线程带来影响,因此在多线程编程中的同步是一个非常重要的问题。
一、线程编程相关函数
线程的创建需要调用第三方的线程库中的库函数,因此需要包含头文件"#include <pthread.h>",同时在编译
程序时需要链接线程库“-lpthread”。
1、线程的创建
函数原型:int pthread_create(pthread_t &tid, const pthread_attr_t *attr,
void *(*routine)(void *), void *arg);
函数参数:tid 线程标识符,线程ID
attr 线程的属性,当使用NULL表示默认属性
routine 函数指针,指向线程的执行函数(线程的回调函数)
arg 传递给线程执行函数的参数
返回值:= 0,表示线程创建成功;
!= 0,表示线程创建失败。
说明:如果进程(主线程)先于其它的线程结束退出,那么进程的地址空间将会被释放;而线程是共享进程的地址
空间,则线程就没有的地址空间,线程无法运行。可以比较下列两个程序中有无sleep时的运行结果:
2、线程的回收
函数原型:int pthread_join(pthread_t tid, void **retval);
函数参数:tid 线程标识符
retval 二级指针retval指向的对象用来存放线程的退出状态。(说明:线程创建成功之后会去执
行线程回调函数,该函数的退出状态就是线程的退出状态。因为线程执行函数的返回值为
void *类型,则应该用一个二级指针来进行接收。如果进程不关心线程的退出状态,则可
以用(void **)NULL;如果用(void **)&retval表示用一级指针retval来保存线程退出
的状态。)
返回值:=0 表示线程回收成功
!=0 表示线程回收失败。
说明:pthread_join函数用于在进程中以阻塞的方式回收线程;如在父进程中用wait函数来回收子进程。如下例,
利用pthread_join函数代替sleep函数。
3、线程的退出
函数原型:void pthread_exit(void *retval);
函数功能:结束当前线程,不可以使用exit函数来退出线程。因为exit函数的作用是退出当前进程,这样改进程
下的所有线程都会退出。
函数参数:retval 线程的退出状态,它可以由进程调用pthread_join函数来进行接收。
二、线程的同步
1、线程的同步概念:
两个线程相互配合完成某一件事情,当一个线程对内存共享数据进行操作的时候,其它线程都不可以对这个内
存地址进行操作,直到该线程完成操作为止。
注意:线程的同步是相互配合,而不是同时进行。例如两条腿合作走路,一次只能迈出一条腿,当一只脚着地
之后再去迈出另一条腿,而不是两条腿一起迈出。
2、信号量
信号量就是操作系统中的PV原子操作,它广泛的用于进程、线程之间的同步和互斥。如果信号量的PV原子操作
用于互斥,那么几个进程(线程)之间往往只设置一个信号量sem;如果信号量用于同步操作,往往会设置多个信号
量,并安排不同的初始值来实现它们之间的顺序执行。操作流程如下:
信号量相关函数介绍:
(1)sem_init函数
函数原型:int sem_init(sem_t *sem, int pshared, unsigned int value);
函数参数:sem 信号量对象
pshared 常取0,表示信号量用于线程之间的通信
value 信号量的初始值
返回值:成功,返回0;失败,返回-1。
(2)sem_wait函数
函数原型:int sem_wait(sem_t *sem);
函数功能:以阻塞的方式获取信号量,P操作,申请资源。
(3)sem_trywait函数
函数原型:int sem_trywait(sem_t *sem);
函数功能:以非阻塞的方式获取信号量,P操作。
(4)sem_post函数
函数原型:int sem_post(sem_t *sem);
函数功能:释放资源,相当于V操作,使信号量的值加1。
(5)sem_getvalue函数
函数原型:int sem_getvalue(sem_t *sem);
函数功能:获取信号量的值
(6)sem_destory函数
函数原型:int sem_destory(sem_t *sem);
函数功能:删除信号量
3、线程同步的实现
使用信号量来完成线程之间的同步工作:设置多个二值信号量,并安排不同的初始值来实现它们之间的执行
顺序。
例题:
(1)利用信号量实现线程之间的同步。
(2)线程1打印“Hello”,线程2打印“World”。
(3)实现“Hello World”的打印效果,循环5次输出“Hello World”
三、线程的互斥
1、线程互斥的概念
线程互斥是指某一资源在同一时刻智能允许一个线程对其进行访问,具有唯一性和排他性。但是互斥无法限制
访问者对资源的访问顺序,即资源的访问是无序的。这是由于多个线程共同竞争一个资源导致的一种现象。
因此在多线程编程中应该尽量的避免使用公共资源,如全局变量、静态局部变量等;应该尽可能的使用非静态
局部变量,为每个线程分配自己独立的栈空间或者堆空间。当时如果不可避免的使用了公共资源,可以利用互斥锁
或者信号量来解决线程互斥的问题。
2、互斥锁
互斥锁通过简单的加锁方法来保证对共享资源的原子操作。互斥锁只有两种状态:上锁和解锁。可以把互斥锁
看成某种意义上的全局变量。在同一时刻只能有一个线程持有互斥锁,拥有互斥锁的线程能够对共享资源进行操作。
如果线程对一个已经上锁的互斥锁进行加锁,则该线程会进入休眠,直到其它线程释放掉互斥锁为止。可以说互斥
锁保证了对共享资源进行原子操作。
互斥锁的基本函数如下:
(1)pthread_mutex_init函数:
函数原型:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex_t *mutexattr);
函数参数:mutex 互斥锁
mutexattr 互斥锁的属性,用NULL表示默认属性
返回值:成功,返回0;失败,返回-1。
(2)pthread_mutex_lock函数
函数原型:int pthread_mutex_lock(pthread_mutex_t *mutex);
函数功能:以阻塞的方式对互斥锁进行加锁
(3)pthread_mutex_trylock函数
函数原型:int pthread_mutex_trylock(pthread_mutex_t *mutex);
函数功能:以非阻塞的方式对互斥锁进行加锁,不成功则返回
(4)pthread_mutex_unlock函数
函数原型:int pthread_mutex_unlock(pthread_mutex_t *mutex);
函数功能:对互斥锁进行解锁
(5)pthread_mutex_destroy函数
函数原型:int pthread_mutex_destroy(pthread_mutex_t *mutex);
函数功能:删除互斥锁
3、线程的互斥
(1)以互斥锁的方式完成线程的互斥
例1:两个线程同时去访问公共资源,并且不互斥锁的情况。
由上图可知,由于两个线程同时访问公有资源,导致了混乱:本来在线程1执行回调函数时应该打印"abcdefgh",
在线程2执行回调函数时应该打印“12345678”,可是结果并不是这样。
例2:对公有资源进行加锁,这样便会以正确的形式进行输出:abcdefgh和12345678;或则是1235567和abcdefgh。
结果如下:
(2)以信号量的方式完成线程的互斥
信号量就是操作系统中的PV原子操作,它广泛的用于进程、线程之间的同步和互斥。如果信号量的PV原子操作
用于互斥,那么几个进程(线程)之间往往只设置一个信号量sem;如果信号量用于同步操作,往往会设置多个信号
量,并安排不同的初始值来实现它们之间的顺序执行。
例3:利用信号量来实现线程的互斥
4、避免使用公共资源
当不可避免的使用公共资源时可以利用信号量或互斥锁来解决线程的互斥问题;但是,应该尽量的避免公共资
源的使用,为每个线程分配独立的资源。
例4:为每个线程分配独立的资源,避免公共资源的使用。(本例中在编译时使用了条件编译)