一、线程是什么
linux下:线程是轻量级进程。(linux下线程和进程并不做明显的区别。线程是PCB进行模拟的.进程的一个执行流)
进程是具有多个线程的线程组。
进程与线程的对比:
1.进程是操作系统分配资源的一个基本单位。
线程是操作系统CPU调度的一个基本单位。
2.进程是拥有一个完整的资源平台。每个线程除了共享资源外也拥有自己的部分数据:线程ID,一组寄存器(保存上下文(一个进 程内容切换到另一个进程内容)数据),栈(防止调用栈混乱),errno(错误码),信号屏蔽字,调度优先级。
3.线程会共享进程所分配的资源:文件描述符,每种信号的处理方式 ,当前的工作目录,用户ID和组ID,同一虚拟地址空间。
线程的优点:
线程能减少并发执行的时间和空间开销
时间上:线程比同一进程的创建时间、终止时间、进程内的线程cancel切换时间都短。
空间上:统一进程的各线程间共享资源和文件资源,可以不通过内核直接通信。
线程的缺点:
缺乏访问控制,健壮性降低,编程难度提高,性能损失
主线程:
1.线程组内的第一个线程,在用户态被称为主线程(main thread),在内核被称为group leader
2.线程组的ID就是第一个线程的ID.group_leader指针则指向自身,即就是主线程的进程
描述符。所以线程组内存在一个线程ID==进程ID,该线程就为线程组的主线程。
线程组tgid = 主线程pid
二、线程控制
头文件:<pthread.h>
链接函数时要使用编译器命令的“-lpthread”选项。
创建一个线程:
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,失败返回错误码(非0值)。
void *thr_start(void *arg)
{
while(1){
printf("thread:%p calling!!\n",(uint64_t*)pthread_self());
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
//pthread_create创建线程,线程入口地址为thr_start
int ret=pthread_create(&tid,NULL,thr_start,NULL);
if(ret!=0){
printf("pthread_create perror\n");
return -1;
}
while(1){
printf("thread %p playing game!!\n",(uint64_t*)pthread_self());
sleep(1);
}
return 0;
}
线程终止:
1.从线程函数return(这种方法对主线程不适用,从main函数return相当于调用exit)。
2.线程调用pthread_exit 来终止自己(谁调用谁退出)。
void pthread_exit (void *value_ptr)
value_ptr :不要指向一个局部变量。
返回值:无返回值,跟进程一样。
3.线程调用pthread_cancel 终止同一进程中的另一个线程(即一个进程可以被另一个进程
取消)。
int pthread_cancel (pthread_t thread)
thread :线程ID
返回值:成功返回0,失败返回错误码
线程等待:
功能:挂起等待,直到id为thread的线程终止。(不会自动释放空间)
int pthread_join (pthread_t thread,void **value_ptr)
thread:线程ID.
value_ptr:指向一个指针,后者指向线程的返回值。
返回值:成功返回0,失败返回错误码
线程分离:(自动释放线程资源)
int pthread_detach(pthread_t thread)
分离可以是线程组内其他线程对目标线程进行分离,也可以是先称自己分离:
pthread_detach(pthread_self());
等待join和分离是冲突的,一个线程不能既是joinable又是分离的。
返回值 :1.线程可以通过return和pthread_exit传递退出返回值
2.如果线程被取消,这个线程的返回值是 -1 PTHREAD_CANCELED
3.线程退出后,如果是joinable状态,则不会自动释放资源,需要其他线程使用
pthread_join接口来等待线程退出后,并获取返回值,释放资源,否则资源无法释放将造成资源泄露。当线程被分离之后,那么这个线程退出时候,自动释放资源不需要被等待。当一个线程被分离之后,那么这个线程是无法被pthread_join等待否则返回EINVAL错误。
三、互斥量
同步:可控时序性。 互斥:同一时间的唯一访问性
互斥锁:同一时间的唯一访问性
互斥锁类型: pthread_mutex_t
互斥量的初始化:pthread_mutex_init (&mutex,&attr)
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict *attr)
mutex:要初始化的互斥量
attr:NULL
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER (不用销毁)
互斥量的加锁:
int pthread_mutex_lock(pthread_mutex_t *mutex)
互斥量的解锁:(任何一个退出的地方)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
互斥量的销毁:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
四、产生死锁的四个必要条件:
1.互斥条件
2.不可剥夺条件(我自己拥有的资源,不能被别人夺走)
3.请求与保持条件(拥有一个资源,再继续请求另一个资源)
4.环路等待条件(彼此互相等待)
死锁的处理:避免死锁----银行家算法
检测死锁---资源分配图简化算法
预防死锁----资源有序分配法
同步,互斥的目的:保持安全
同步:可控时序性。 互斥:同一时间的唯一访问性
加锁:在访问临界资源之前 解锁:在每一个线程的退出地方
五、条件变量
生产者--消费者模型:
三种关系:生产者--生产者 (互斥)
消费者--消费者(互斥)
生产者--消费者 (同步,互斥)
两个角色:生产者,消费者
一种机制:互斥机制
初始化:
int pthread_cond_init (pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
cond :要初始化的条件变量
attr :NULL
销毁:
int pthread_cond_destroy (pthread_cond_t *cond)
等待条件满足:(包括解锁和休眠操作,两个都是原子操作,不能被打断)
int pthread_cond_wait (pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
cond :要在这个条件变量上等待
mutex :互斥量
唤醒等待: 唤醒等待在这个条件变量上的线程
int pthread_cond_broadcast (pthread_cond_t *cond);
int pthread_cond_signal (pthread_cond_t *cond);
六、POSIX信号量
(头文件:#include<semaphore.h>)
和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。只不过POSIX可以用于线程间同步。
信号量也可以实现同步和互斥。
初始化信号量(一次只能初始化一个):
int sem_init(sem_t *sem ,int pshared, unsigned int value)
pshared : 0 表示线程间共享 非零表示进程间共享
value :信号量初值
销毁信号量:
int sem_destroy(sem_t *sem)
等待信号量:
int sem_wait(sem_t *sem)
功能:等待信号量,会将信号量的值减1(相当于p操作)
发布信号量:
int sem_post(sem_t *sem)
功能:发布信号量,表示资源使用完毕,可以归还资源了,将信号量加1(相当于v操作)