目录
线程概念
1.定义
是一个轻量级的进程,为了提高系统的性能引入线程
线程和进程都参与统一的调度。
在同一个进程中创建的线程共享该进程的地址空间。
2.进程和线程区别
共性:都为操作系统提供了并发执行能力
不同点:调度和资源:线程是系统调度的最小单位,进程是资源分配的最小单位
地址空间方面:同一个进程创建的多个线程共享进程的资源;进程的地址空间相互独立
通信方面:线程通信相对简单,只需要通过全局变量可以实现,但是需要考虑临界资源访问的问题;进程通信比较复杂,需要借助进程间的通信机制(借助3g-4g内核空间)
安全性方面:线程安全性差一些,当进程结束时会导致所有线程退出;进程相对安
3.函数接口
创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
参数:thread:线程ID
attr:线程属性,一般设为NULL
start_routine:函数指针,传递函数名,
函数定义void *fun(void *arg){}
arg:给前面的函数传参
返回值:成功:0
失败:错误码
结束线程
int pthread_exit(void *value_ptr)
功能:用于退出线程的执行
参数:value_ptr:线程退出时返回的值
返回值:成功 : 0
失败:errno
回收线程
int pthread_join(pthread_t thread, void **value_ptr)
功能:用于等待一个指定的线程结束,阻塞函数
参数:thread:创建的线程对象
value_ptr:指针*value_ptr指向线程返回的参数
返回值:成功 : 0
失败:errno
线程分离
int pthread_detach(pthread_t thead)
功能:让线程分离,线程结束时自动回收线程资源
参数:thead:线程ID
获取线程
pthread_t pthread_self(void)
功能:获取当前线程的ID
返回值:获取的线程ID
练习
主线程循环从终端输入字符串,子线程循环打印主线程输入的字符串,当输入"quit"结束程序
#include<stdio.h>
#include<string.h>
#include<pthread.h>
char buf[32]="";
int flag=0; //标志位 置0
void *headler(void *arg)
{
while(1)
{
if(flag==1){
if(!strcmp(buf,"quit"))
break;
printf("buf:%s\n",buf);
flag=0;
}
}
}
int main(int argc, char const *argv[])
{
pthread_t tid; //线程ID
//创建线程
if(pthread_create(&tid,NULL,headler,buf)!=0)
{
printf("create thread err");
return -1;
}
while(1)
{
scanf("%s",buf);//主线程循环输入
flag=1;
if(!strcmp(buf,"quit"))//当输入quit时退出
break;
}
pthread_join(tid,NULL);//回收线程
return 0;
}
运行结果:
线程同步
1.概念
同步(synchronization)指的是多个任务(线程)按照约定的顺序相互配合完成一件事情
2.同步机制
通过信号量实现线程间同步。
信号量:由信号量来决定线程是继续运行还是阻塞等待,信号量代表某一类资源,其值表示系统中该资源的数量
信号量是一个受保护的变量,只能通过三种操作来访问:初始化、P操作(申请资源)、V操作(释放资源)
信号量的值为非负整数
3.特性
P操作:
当信号量的值大于0时,可以申请到资源,申请资源后信号量的值减1
当信号量的值等于0时,申请不到资源,函数阻塞
V操作:不阻塞,执行到释放操作,信号量的值加1
4.函数接口
int sem_init(sem_t *sem, int pshared, unsigned int value)
功能:初始化信号量
参数:sem:初始化的信号量对象
pshared:信号量共享的范围(0: 线程间使用 非0:1进程间使用)
value:信号量初值
返回值:成功 0
失败 -1
int sem_wait(sem_t *sem)
功能:申请资源 P操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:此函数执行过程,当信号量的值大于0时,表示有资源可以用,则继续执行,同时对信号量减1;当信号量的值等于0时,表示没有资源可以使用,函数阻塞
int sem_post(sem_t *sem)
功能:释放资源 V操作
参数:sem:信号量对象
返回值:成功 0
失败 -1
注:释放一次信号量的值加1,函数不阻塞
线程互斥
1.概念
临界资源:一次仅允许一个进程/线程所使用的资源
临界区:指的是一个访问共享资源的程序片段
互斥:多个线程在访问临界资源时,同一时间只能一个线程访问
互斥锁:通过互斥锁可以实现互斥机制,主要用来保护临界资源,每个临界资源都由一个互斥锁来保护,线程必须先获得互斥锁才能访问临界资源,访问完资源后释放该锁。如果无法获得锁,线程会阻塞直到获得锁为止。
2.函数接口
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr)
功能:初始化互斥锁
参数:mutex:互斥锁
attr: 互斥锁属性 // NULL表示缺省属性
返回值:成功 0
失败 -1
int pthread_mutex_lock(pthread_mutex_t *mutex)
功能:申请互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
注:和pthread_mutex_trylock区别:pthread_mutex_lock是阻塞的;pthread_mutex_trylock不阻塞,如果申请不到锁会立刻返回
int pthread_mutex_unlock(pthread_mutex_t *mutex)
功能:释放互斥锁
参数:mutex:互斥锁
返回值:成功 0
失败 -1
int pthread_mutex_destroy(pthread_mutex_t *mutex)
功能:销毁互斥锁
参数:mutex:互斥锁
练习
案例:两个线程,定义全局数组,一个线程循环打印数组数据,另一个线程循环倒置
int a[10] = {0,1,2,3,4,5,6,7,8,9};
#include<stdio.h>
#include<pthread.h>
int a[10]={0,1,2,3,4,5,6,7,8,9};
pthread_mutex_t lock;
void *swap_handler(void *arg)//循环打印数组
{
int i;
while(1)
{
//上锁
pthread_mutex_lock(&lock);
for(i=0;i<10;i++)
printf("%d",a[i]);
printf("\n");
//解锁
pthread_mutex_unlock(&lock);
sleep(1);
}
}
void *reswap_handler(void *arg)//循环打印倒置数组
{
int i,t;
while(1)
{
//上锁
pthread_mutex_lock(&lock);
for(i=0;i<5;i++){
t=a[i];
a[i]=a[9-i];
a[9-i]=t;
}
//解锁
pthread_mutex_unlock(&lock);
}
}
int main(int argc, char const *argv[])
{
pthread_t t1,t2;
pthread_create(&t1,NULL,swap_handler,NULL);
pthread_create(&t2,NULL,reswap_handler,NULL);
if(pthread_mutex_init(&lock,NULL)!=0)
{
printf("lock err");
return -1;
}
pthread_join(t1,NULL);
pthread_join(t2,NULL);
return 0;
}
运行结果
3.死锁
是指两个或两个以上的进程/线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去
死锁产生的四个必要条件:
1、互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
2、不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
3、请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
4、循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
注意:当上述四个条件都成立的时候,便形成死锁。当然,死锁的情况下如果打破上述任何一个条件,便可让死锁消失。