目录
4、分离线程pthread_detach,有可能还没执行到这个语句的时候,线程就已经执行完毕了
一、线程的概述
进程并不执行什么,只是维护应用程序所需要的各种资源,而线程才是真正的执行实体。所以,线程是轻量级的进程,在linux环境下线程的本质就是进程。为了让进程完成一定的工作,一个进程至少包含一个线程。
是CPU执行调度的基本单位。
线程依赖于进程,线程共享进程的资源。 线程独立的资源:计数器、寄存器、栈等
线程函数列表安装:apt -get install manpages-posix-dev
这是第三方库,使用man -k pthread 查看
二、线程的特点
1)线程是轻量级进程,也有PCB,创建线程使用的底层函数和进程一样,都是clone
如果复制对方地址空间就是产生一个进程,如果共享对方地址空间就是产生一个线程
2)从内核看进程和线程是一样的,都有各自不同的PCB
3)进程可以蜕变成线程。
4)线程是最小的调度执行单位,进程是最小的资源分配单位
5)线程共享资源:文件描述符、当前工作目录,各种信号的处理方式
线程非共享资源:线程id、处理器现场和栈指针(内核栈)、独立的栈空间(用户栈空间)、errno变量、 信号屏蔽字、调度优先级。
线程的优点:
提高程序并发性、开销小、通信、共享数据方便
线程的缺点:
库函数、不稳定。 调试、编写困难、gdb不支持。对信号支持不友好。
优点相对突出,缺点均不是硬伤。
三、线程的API
1、查看线程号pthread_self
头文件:
#include<pthread.h>
函数原型:
pthread_t pthread_self(void)
功能:
获取线程号
参数:
无
返回值:
调用线程的线程ID
2、创建线程:pthread_create 函数
头文件:
#include <pthread.h>
函数原型:
int pthread_create(pthread t*thread ,
const pthread_attr_t *attr,
void *(*stant routine)(void *),
void *arg );
功能:
创建一个线程
参数:
thread:.线程标识符地址。
attr:线程履姓结构体地址..通赏设置为NULL。
start_routine:.线程函数的入口地址。
arg:传给线程函数的参数
返回值:
成功:0
失败:非0
案例:创建两个线程:
#include <stdio.h>
#include <pthread.h>
#include<unistd.h>
void *fun01(void *arg);
void *fun02(void *arg);
int main(int argc, char const *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun01,"线程一任务A");
pthread_create(&tid2,NULL,fun02,"线程二任务B");
getchar();
return 0;
}
void *fun01(void *arg)
{
int i=0;
while(1)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
}
}
void *fun02(void *arg)
{
int i=0;
while(1)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
}
}
此时的LWP并不是线程号,linux系统将线程也当做进程分配进程号,ps查看发现进程号不是连续就是因为,每个进程中有线程也分配了进程号。
3、回收线程资源pthread_join函数:带阻塞
头文件:
#include<pthread.h>
函数原型:
int pthread_join(pthread_t thread,void **retval)
功能:
等待线程结束(此函数会阻塞),并回收线程资源,类似进程的wait()函数。如果线程已经结束,那么该函数会立即返回。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址返回值:
返回值:
成功:0
0失败:非0
案例:
#include <stdio.h>
#include <pthread.h>
#include<unistd.h>
void *fun01(void *arg);
void *fun02(void *arg);
int main(int argc, char const *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun01,"线程一任务A");
pthread_create(&tid2,NULL,fun02,"线程二任务B");
pthread_join(tid1,NULL);//会 一直等待任务1结束在执行下面代码
printf("work1 over\n");
pthread_join(tid2,NULL);
printf("work2 over\n");
return 0;
}
void *fun01(void *arg)
{
int i=0;
while(i<5)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
}
}
void *fun02(void *arg)
{
int i=0;
while(i<3)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
}
}
pthread_join第二个参数可以获取返回值
案例:
#include <stdio.h>
#include <pthread.h>
#include<unistd.h>
void *fun01(void *arg);
void *fun02(void *arg);
int main(int argc, char const *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun01,"线程一任务A");
pthread_create(&tid2,NULL,fun02,"线程二任务B");
void *p1 = NULL;
void *p2 =NULL;
pthread_join(tid2,&p2);
printf("work2 over\n%s\n",(char*)p2);
pthread_join(tid1,&p1);
printf("work1 over\n%s\n",(char*)p1);
return 0;
}
void *fun01(void *arg)
{
int i=0;
while(i<5)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
}
return (void*)"work1";
}
void *fun02(void *arg)
{
int i=0;
while(i<3)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
}
return (void*)"work2";
}
4、分离线程pthread_detach,有可能还没执行到这个语句的时候,线程就已经执行完毕了
头文件:
#include<pthread.h>
函数原型:
int pthread_detach(pthread_t thread)
功能:
使线程和当前进程分离,分离不代表不依赖,当线程结束时候,由系统自动回收线程资源。
参数:
thread:被等待的线程号。
返回值:
成功:0
0失败:非0
5、线程的退出和取消
线程的退出:
在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程我们也可以通过一下三种方式在不影响进程的情况下停止他的控制流。线程从执行函数中返回。线程可以被其他线程所取消。
1)线程退出pthread_exit函数
头文件:
#include<pthread.h>
函数原型:
void pthread_exit(void *retval)
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针
返回值:
无
案例:
#include <stdio.h>
#include <pthread.h>
#include<unistd.h>
void *fun01(void *arg);
int main(int argc, char const *argv[])
{
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun01,"线程一任务A");
pthread_join(tid1,NULL);
printf("任务A结束了,没有被线程一结束所影响\n");
return 0;
}
void *fun01(void *arg)
{
int i=0;
while(1)
{
printf("%s执行:%d\n",(char*)arg,i++);
sleep(1);
if(i==5)
{
pthread_exit(NULL);
}
}
}
线程结束后,不会影响主线程后面代码的执行
2)线程取消函数pthread_cancel
头文件:
#include<pthread.h>
函数原型:
int pthread_cancel(pthread_t thread)
功能:
取消(杀死)线程
参数:
thread:线程ID
返回值:
成功0
失败:出错编号
线程的取消并不是实时的,有一定的延时。
四、线程的属性
案例1:创建线程的时候就设置线程分离,避免还没分离就结束
以前:
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun01,NULL);
X
X
X
X
X
pthread_detach(tid1,NULL);
现在:
初始化线程属性的函数pthread_attr_init,这个函数必须在pthread_create之前调用
int pthread_attr_init(pthread_attr_t *attr)
销毁线程属性所占用的资源函数
int pthread_attr_destroy(pthread_attr_t *attr)
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
pthread_create(tid,&attr,fun1,NULL);
五、线程的同步互斥
1、同步互斥的概念:
现代操作系统基本都是多任务操作系统,即同时有大量可调度实体在运行。在多任务操作系统中,同时运行的多个任务可能:都需要访问/使用同一种资源多个任务之间有依赖关系,某个任务的运行依赖于另一个任务这两种情形是多任务编程中遇到的最基本的问题,也是多任务编程中的核心问题,同步和互斥就是用于解决这两个问题的。
互斥:同一时间只能有一个任务执行,谁先运行不确定。
同步:同一时间只能有一个任务执行,但是有顺序的运行。
同步是特殊的互斥
互斥锁mutex(互斥量)类型是pthread_mutex_t
互斥锁是一种简单加锁的办法来控制对共享资源的访问,互斥锁只有两种状态,加锁lock/解锁unlock
互斥锁的操作如下
1)在访问共享资源的临界区域前,对互斥锁上锁
2)在完成访问后再释放互斥锁上的锁
3)对互斥锁进行加锁后,任何想对互斥锁加锁的线程将被阻塞,直到解锁。
安装对应手册:sudo apt-get install manpages-posix-dev
2、API
1、Pthread_mutex_init函数 初始化锁
头文件:
#include<pthread.h>
函数原型:
int pthread_mutex_init(pthread_mute_t *mutex,const pthread_mutexattr_t *attr)
功能:
初始化一个互斥锁
参数:
mutex:互斥锁地址
attr:设置互斥锁的属性,通常可以采用默认属性,即NULL
可以使用宏PTHREAD_MUTEX_INITIALIZER静态初始化互斥锁。比如:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
返回值:
成功:返回0,成功申请的锁默认是打开的
失败,非0错误码
2、pthread_mutex_destory销毁锁
头文件:
#include<pthread.h>
函数原型:
int pthread_mutex_destory(pthread_mutex_t *mutex)
功能:
销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
mutex:互斥锁地址
返回值:
成功:0
失败:非0,错误码
3、pthread_mutex_lock 申请上锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_mutex_lock(pthread_mutex_t *mutex)
功能:
对互斥锁上锁。如已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁
参数:
mutex:互斥锁地址
返回值:
成功:0
失败:非0错误码
如果调用函数的时候,若互斥锁未上锁,则上锁返回0
如果已经上次,则返回失败,EBUSY
4、pthread_mutex_unlock解锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_mutex_unlock(pthread_mutex_t *mutex)
功能:
对指定的互斥锁解锁
参数:
mutex:互斥锁地址
返回值:
成功:0
失败:非0错误码
案例:使用互斥锁实现打印机
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//创建一把互斥锁
pthread_mutex_t mutex;
void *fun1(void*arg)
{
//上锁
pthread_mutex_lock(&mutex);
int i=0;
char *str = (char*)arg;
while(str[i]!='\0')
{
printf("%c",str[i++]);
fflush(stdout);
sleep(1);
}
//解锁
pthread_mutex_unlock(&mutex);
}
void *fun2(void*arg)
{
//上锁
pthread_mutex_lock(&mutex);
int i=0;
char *str = (char*)arg;
while(str[i]!='\0')
{
printf("%c",str[i++]);
fflush(stdout);
sleep(1);
}
//解锁
pthread_mutex_unlock(&mutex);
}
int main(int argc, char const *argv[])
{
//初始化锁
pthread_mutex_init(&mutex,NULL);
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,fun1,"hello");
pthread_create(&tid2,NULL,fun2,"world");
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
//摧毁互斥锁
pthread_mutex_destroy(&mutex);
return 0;
}
3、死锁
互斥条件:只能被一个进程使用,其他进程请求该资源时,只能等待,知道资源使用完毕后被释放。
请求和保持条件:程序已经保持了 至少一个资源,但是又提出了新要求,而这个资源被其他进程占用,自己占用资源却保持不放。
不可抢占条件:进程已经获得的资源没有使用完,不能被抢占。
循环等待条件:必然存在一条循环链
1)处理死锁的思路
预防死锁:破坏死锁的四个必要条件之一或者多个来预防死锁。
1、破坏请求和保持条件
协议一:所有进程开始前,必须一次性申请所有资源,这样运行的时候就不会在提出资源请求,破坏了申请条件。即使有一种资源不能满足需求,也不会给它分配正在空闲的资源,这样他就没有资源,就破坏了保持条件,从而预防死锁的发生。
协议二:允许一个进程只获得初期的资源就开始运行,然后再把运行完的资源释放出来,然后再请求新的资源。破坏不可抢占条件,当一个已经保持了某种不可抢占资源的进程,提出新资源请求不能别满足的时候,他必须释放已经保持的所有资源,以后需要的的时候再申请。 破坏循环等待条件 对系统中的所有资源类型进行线性排序,然后规定每个进程必须按序列号递增的顺序请求资源。假如进程请求到了一些序列号较高的资源,然后有精求一个序列较低的资源时,必须先释放相同和更高序号的资源后才能申请低序号的资源。多个同类资源必须一起请求。
预防死锁和破坏死锁的区别就是,在资源分配的过程中,用某种方式防止系统进入不安全的状态。检测死锁和运行时出现死锁,能及时发现死锁,把程序解脱出来,解除死锁 发生死锁后,解脱进程,通常撤销进程,回收资源,再分配给正处于阻塞状态的进程
读写锁
当任务在写的时候,其他任务不能读写。
当任务在读的时候,其他任务不能写,但是可以读。
API
1、初始化读写锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr)
功能:
用来初始化rwlock所指向的读写锁
参数:
rwlock:指向要初始化的读写锁
attr:读写锁的属性指针,如果为NULL,则会使用默认的属性来初始化读写锁。
可以使用宏PTHREAD_RWLOCK_INITALIZER 来静态初始化读写锁。
返回值:
成功:0,读写锁的状态为已初始化和已解锁
失败:非0错误码
2、销毁读写锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_rwlock_destory(pthread_rwlock_t *rwlock)
功能:
用于销毁一个读写锁,并且释放所有相关联的资源(即由pthread_rwlock_init()自动初始化的申请的资源)
参数:
rwlock:读写锁
返回值:
成功:0
失败:非0错误码
3、申请读锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_rwlock_rdlock(pthread_rwlock_t *rwlock)
功能:
以阻塞的方式在读写锁上获取读锁。
如果没有写者持有该锁,并且没有写着阻塞在该锁上,则调用线程会获取读锁
如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。
线程可以成功调用此函数N次,但是之后该线程必须调用pthread_rwlock_unlock函数N次来解除锁定。
参数:
rwlock:读写锁
返回值:
成功:0
失败:非0错误码
4、申请写锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_rwlock_wrlock(pthread_rwlock_t *rwlock)
功能:
在读写锁上获取写锁。
如果没有写着持有该锁,并且没有写着读者持有该锁,则调用线程会获取写锁
如果调用线程未获取锁,则他将阻塞直到他获取了该锁。
参数:
rwlock:读写锁
返回值:
成功:0
失败:非0错误码
5、释放读写锁
头文件:
#include<pthread.h>
函数原型:
int pthraed_rwlock_unlock(pthread_rwlock_t *rwlock)
功能:
释放读写锁
参数:
rwlock:读写锁
返回值:
成功:0
失败:非0错误码