线程基本概念
线程是一个轻量级的进程
1.线程的创建
1.线程必须位于进程空间内部
2.线程独享栈区,剩余的文本段、数据段、堆区与进程共享
2.线程的调度
和进程调度完全相同
宏观并行,微观串行
3.线程的消亡
线程代码执行结束,空间不回收会成为僵尸线程,需要回收线程空间
进程和线程的区别
1.进程是操作系统资源分配的最小单元
2.线程是CPU任务调度的最小单元
多进程和多线程的优缺点
1.执行效率:
多线程 > 多进程
多进程需要在不同的进程空间内部切换调度任务
多线程只需要在同一进程空间内部切换调度任务
2.安全性:
多进程 > 多线程
多进程一个进程任务异常结束不会影响其余任务
多线程一个线程任务异常结束可能导致进程异常结束,会导致进程中其余线程也随进程一起结束
3.通信效率:
多线程 > 多进程
多线程全局变量共享,通信直接使用全局变量即可
多进程没有共享空间,通信需要使用其余的进程间通信方式完(管道、套接字、信号等)成
4.编程复杂性:
多进程 > 多线程
多线程全局变量共享,通信简单但需要考虑资源竞争问题,需要引入互斥锁防止资源竞争
多进程不用考虑资源竞争问题
同一软件下的多任务考虑用多线程
不同软件下的多任务考虑用多进程
进程和线程实际效率差不多
线程相关的函数接口
进程 线程
fork 创建进程空间 创建线程 pthread_create
exit 退出进程 退出线程 pthread_exit
wait 回收进程空间 回收线程 pthread_join
(1)pthread_create
pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能:
在进程中创建一个线程任务
参数:
thread:存放线程任务ID号空间首地址
attr:线程属性 (默认属性 NULL)
start_routine:线程任务函数
arg:对线程函数的传参
返回值:
成功返回0
失败返回错误码编译: gcc filename.c -lpthread
(2)pthread_self
pthread_self
pthread_t pthread_self(void);
功能:
获得当前线程的ID号
参数:
缺省
返回值:
成功返回线程的ID号
(3)pthread_exit
pthread_exit
void pthread_exit(void *retval);
功能:
退出线程任务
参数:
retval:线程结束的状态
返回值:
缺省
(4)pthread_join
pthread_join
int pthread_join(pthread_t thread, void **retval);
功能:
回收线程任务
参数:
thread:要回收的线程ID号
retval:存放线程结束状态的值的空间首地址
返回值:
成功返回0
失败返回错误码注意:
pthread_join具有阻塞功能,线程不结束,会阻塞等到直到线程结束回收线程空间
pthread_join具有同步功能
#include "../head.h"
void * threadfun(void *arg)
{
/*线程内部获取TID*/
printf("%#lx线程要开始执行啦\n",(unsigned long)pthread_self());
sleep(5);
pthread_exit("我要结束啦!");
return NULL;
}
int main()
{
pthread_t tid;
int ret=0;
void *pststus=NULL;
/*创建线程*/
ret=pthread_create(&tid, NULL, threadfun, NULL);
if(ret==-1)
{
fprintf(stderr,"failed to create thread:%d",ret);
return -1;
}
printf("%#lx线程创建成功\n",(unsigned long)tid);
/*回收线程空间*/
pthread_join(tid,&pststus);
printf("%#lx线程空间被回收,线程结束状态为:%s\n",(unsigned long)tid,(char *)pststus);
return 0;
}
线程的分离属性
线程在结束时由操作系统自动回收线程空间
(1)pthread_attr_init
pthread_attr_init
int pthread_attr_init(pthread_attr_t *attr);
功能:
初始化线程属性
(2)pthread_attr_destroy
pthread_attr_destroy
int pthread_attr_destroy(pthread_attr_t *attr);
功能:
销毁线程属性
(3) pthread_attr_setdetachstate
pthread_attr_setdetachstate
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:
将线程设置为分离属性
参数:
attr:线程属性
detachstate:PTHREAD_CREATE_DETACHED 分离属性
PTHREAD_CREATE_JOINABLE 加入属性(默认)
#include "../head.h"
void *thread1fun(void *arg)
{
printf("线程1 %#lx开始执行\n",(unsigned long)pthread_self());
pthread_exit("线程结束啦");
}
void *thread2fun(void *arg)
{
printf("线程2 %#lx开始执行\n",(unsigned long)pthread_self());
pthread_exit("线程结束啦");
}
void *thread3fun(void *arg)
{
printf("线程3 %#lx开始执行\n",(unsigned long)pthread_self());
sleep(3);
pthread_exit("线程结束啦");
}
int main()
{
pthread_t tid[3];
int cnt=0;
int ret=0;
void *status=NULL;
void *(*p[3])(void *)={thread1fun,thread2fun,thread3fun};
pthread_attr_t attr;//设置线程属性
pthread_attr_init(&attr);//初始化线程属性
//将线程属性设置为分离属性,线程结束自动被系统回收
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
while(1)
{
ret=pthread_create(&tid[cnt],&attr,p[cnt],NULL);
if(ret==-1)
{
fprintf(stderr,"failed to create phread:%d\n",ret);
return -1;
}
else
{
printf("%#lx线程创建成功\n",(unsigned long)tid[cnt]);
}
if(cnt==2)
{
break;
}
cnt++;
}
while(1)
{
}
#if 0
for(cnt=0;cnt<3;cnt++)
{
pthread_join(tid[cnt],&status);
printf("%#lx线程%d被回收,回收状态为:%s\n",(unsigned long)tid[cnt],cnt+1,(char *)status);
}
#endif
return 0;
}
互斥锁
概念
1.资源:
资源是有限的,在程序运行过程中,一段代码、一段空间、一个变量、CPU、内存都可以看做资源2.互斥锁:
是一种资源,当一个线程任务加锁,其余线程任务无法再次加锁,直到解锁后才能加锁
互斥锁主要是用来防止多个线程任务竞争某个资源
加锁和解锁中间的代码称为临界代码也称为临界区互斥锁不能同步,多个任务依然保持异步执行,但是可以解决资源竞争
原子操作:最小的一次CPU操作,在执行原子操作时不会切换调度任务
函数接口
(1) pthread_mutex_init
pthread_mutex_init
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
功能:
初始化互斥锁
参数:
mutex:互斥锁
attr:互斥锁属性 NULL
返回值:
成功返回0
失败返回-1
(2) pthread_mutex_destroy
pthread_mutex_destroy
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
销毁互斥锁
参数:
mutex:互斥锁
返回值:
成功返回0
失败返回-1
(3) pthread_mutex_lock
pthread_mutex_lock
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
加锁
参数:
mutex:互斥锁
返回值:
成功返回0
失败返回-1
(4)pthread_mutex_unlock
pthread_mutex_unlock
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
解锁
参数:
mutex:互斥锁
返回值:
成功返回0
失败返回-1
#include "../head.h"
int num=0;
int value1=0;
int value2=0;
pthread_mutex_t lock;
void *thread1fun(void *arg)
{
while(1)
{
pthread_mutex_lock(&lock);//加锁
value1=num;
value2=num;
num++;
pthread_mutex_unlock(&lock);//解锁
}
return NULL;
}
void *thread2fun(void *arg)
{
//pthread_mutex_lock(&lock);//加锁,加到这while无限循环,解锁不了了
while(1)
{
pthread_mutex_lock(&lock);//加锁
if(value1!=value2)
{
printf("value1=%d,value2=%d\n",value1,value2);
}
pthread_mutex_unlock(&lock);//解锁
}
//
return NULL;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
pthread_mutex_init(&lock,NULL);//初始化互斥锁
pthread_create(&tid1,NULL,thread1fun,NULL);
pthread_create(&tid1,NULL,thread2fun,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
pthread_mutex_destroy(&lock);//销毁互斥锁
}
死锁
概念
多线程加锁解锁导致多个任务均无法向下执行的状态称为死锁状态简称为死锁
死锁产生的4个必要条件:
1.互斥条件:同一把锁不能被多个线程任务同时锁定
2.不剥夺条件:不剥夺条件是指一个线程任务已经锁定了一个资源,不能被其他线程任务抢占,直到它使用完该资源
3.请求和保持条件:如果没有拿到锁资源一直申请获得锁资源
4.循环等待条件:如果没有得到锁资源会一直等待
如何避免产生死锁:
1.加锁顺序保持一致
2.打破互斥条件、不可剥夺条件(不建议使用)
3.使用pthread_mutex_trylock替代pthread_mutex_lock
信号量
概念
实现多线程间同步
信号量是一种资源(可以初始化、销毁、申请、释放)
申请:资源数 > 0 申请操作让资源数-1
资源数 == 0 申请操作会阻塞,直到资源数不为0,申请得到资源后继续向下执行
释放:资源数+1
释放操作让资源数+1
函数接口
(1)sem_init
sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
对信号量的初始化
参数:
sem:信号量空间首地址
pshared:
0 线程间共享
非0 进程间共享
value:信号量的初始值
返回值:
成功返回0
失败返回-1
(2) sem_destroy
int sem_destroy(sem_t *sem);
功能:
信号量的销毁
(3) sem_destroy
int sem_destroy(sem_t *sem);
功能:
信号量的销毁
(4) sem_wait
int sem_wait(sem_t *sem);
功能:
申请信号量(资源数-1)
资源数为0阻塞等待直到有资源申请后继续向下执行
(5) sem_post
int sem_post(sem_t *sem);
功能:
释放信号量(资源数+1)
#include "../head.h"
char tmpbuff[100]={0};
sem_t sem_r;//读信号量
sem_t sem_w;//写信号量
void *thread1fun(void *arg)
{
while(1)
{
sem_wait(&sem_w);//申请一个写
gets(tmpbuff);
sem_post(&sem_r);//释放一个读
}
return NULL;
}
void *thread2fun(void *arg)
{
while(1)
{
sem_wait(&sem_r);//申请一个读
printf("tmpbuff=%s\n",tmpbuff);
sem_post(&sem_w);//释放一个写
}
return NULL;
}
int main()
{
pthread_t tid1;
pthread_t tid2;
sem_init(&sem_w,0,1);//写初始为0,程序开始只能执行写操作,写了只后才能读
sem_init(&sem_r,0,0);//读初始为1,buff空间中还没写不能读
pthread_create(&tid1,NULL,thread1fun,NULL);
pthread_create(&tid2,NULL,thread2fun,NULL);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
sem_destroy(&sem_r);
sem_destroy(&sem_w);
}