线程底层:
- 在linux下PCB模拟实现进程,通俗的说就是一个大的PCB下有很多小的PCB,称为轻量级进程。
- 在一个进程中有多个PCB,而这些所有的PCB共用同一份虚拟地址空间。而这些PCB共同基层构成一个PCB组,一个线程组就为一个进程。
3.一个线程是程序执行的基本单位,进程是资源调度的基本单位。 - 线程是CPU调用的基本单位。
线程之间的资源共享 :
独有:
函数调用栈
寄存器 (不是硬件,而是程序运行信息等等)
信号屏蔽字(信号是发给进程的,而一个信号发给一个进程,只要一个线程处理就行) ,errno(防止相互影响), 线程ID, 调度优先级
共享:
共享虚拟地址空间,文件描述符表 ,当前工作路径,用户id,组id,每种信号处理的方式
多进程和多线程用哪一个好
多线程任务的优缺点:
多线程共用进程大部分资源
1.线程间通信更加方便,除了进程通信的方式外还有全局数据/传参等。
2.创建销毁一个现成的成本较进程更加低
3.线程间的调度较于进程更低
缺点:线程之间缺乏访问控制,有些系统调用/异常针对的是整个进程,稳定性比进程的低。例如一个段错误所有进程就挂了。而进程在处理可能发生错误的时候可以用子进程去,子进程挂了父进程还在,还可以处理。
线程控制
1.在linux下操作系统并没有提供线程的控制系统调用接口;因此大佬封装了一套进程库,在linux下在三号手册中。
2.使用库函数实现创建的线程称之为用户态线程,这个用户态线程在内核中使用一个轻量级进程实现调度。
3.linux下的进程:用户态线程+轻量级进程
线程创建
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,失败返回错误码
当线程创建后可以用:ps-esL 查看线程信息
线程终止:
- return不能再主函数中使用,主函数return 会直接退出进程
pthread_exit(void *retval);
退出线程自身,谁调用谁退出 retval为返回值in pthread_cancel(pthread_t thread);
取消其他线程,让其他线程退出
thread 为要取消线程的ID。
线程退出后,默认不会自动释放资源,(保存自己的退出结果在线程独有的地址空间中)造成资源泄露。
4.主线程退出后,其他线程照样可以运行- 处于joinable属性的线程必须被等待,否则造成资源泄露
- int pthread_join(pthread_t thread,void** val) 等待指定线程退出 val未返回值
// 通过这个就可以明白堆上申请的内存不会被程序释放掉
// return 和 exit 都可以让程序退出
// pthread_cancel(pthread_t tid) 可以让任意线程退出
// pthread_detach(pthread_t tid) 将线程设置datach属性 这时
// 如果进行pthread_join(pthread_t tid, void* ar); 相当于
// 不执行操作
// 一旦主线程退出那么所有线程都退出
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
void* pthread_1(void* agr)
{
printf("pthread_1 runing 1 \n");
int* p = (int*)malloc(sizeof(int));
*p = 1;
return (void*)p;
}
void* pthread_2(void* arg)
{
printf("pthread_2 runing 2 tid:%lu \n ", pthread_self());
long* p = (long*)malloc(sizeof(long));
*p = 1000;
return (void*)p;
}
void* pthread_3(void* arg)
{
sleep(1);
printf("pthread_2 P:%ld", *(long*)arg);
free(arg);
while(1)
{
printf("pthread_3 ruing \n");
sleep(1);
}
return NULL;
}
void* pthread_4(void* arg)
{
while(1)
{
printf("pthread_4 runing\n");
sleep(1);
}
}
void* pthread_5(void* arg)
{
}
int main()
{
pthread_t tid;
void* red;
pthread_create(&tid, NULL, pthread_1, NULL);
pthread_join(tid, &red);
printf("pthread_1 exit tid: %lu red: %d\n ", tid , *(int*)red);
free(red);
pthread_create(&tid , NULL, pthread_2, NULL);
pthread_join(tid, &red);
printf("pthread_2 exit tid: %lu red: %d \n", tid , *(int*)red);
pthread_create(&tid, NULL, pthread_3, (void*)red);
sleep(5);
pthread_cancel(tid);
pthread_join(tid, NULL);
printf("第一阶段完毕");
pthread_t pthread;
pthread_create(&tid, NULL, pthread_4, NULL);
// pthread_create(&pthread, NULL, pthread_5, NULL);
sleep(4);
pthread_detach(tid);
pthread_join(tid, NULL);
printf("------------------主线程退出\n ");
sleep(5);
return 0;
}
线程1和线程2分别是exit和return退出, 两者功能相同, 并且程序员在堆上开辟的内存线程不会释放在线程3中还可以访问
处于detach属性的线程, join不会被阻塞, 直接会运行下面的代码
在主线程打印完 “主线程退出” 这句话后主线程陷入休眠5秒, 之后再退出, 之后主线程退出程序结束, 这样就证明不管其他线程有没有运行完, 整个进程直接退出
死锁代码演示
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
pthread_mutex_t m;
void *runodd(void *d)
{
int i=0;
for(i=1;;i+=2)
{
pthread_mutex_lock(&m);
printf("奇数:%d\n",i);
usleep(100);
pthread_mutex_unlock(&m);
}
}
void *runeven(void *d)
{
int i=0;
for(i=0;;i+=2)
{
pthread_mutex_lock(&m);
printf("偶数:%d\n",i);
usleep(100);
pthread_mutex_unlock(&m);
}
}
int main()
{
pthread_t todd,teven;
pthread_mutex_init(&m,0);
pthread_create(&todd,0,runodd,0);
pthread_create(&teven,0,runeven,0);
sleep(1);
printf("外部强制停止todd线程\n");
pthread_cancel(todd); /*给线程发送退出信号,当遇到取消点的时候,进程就会退出
使用某些函数就会出现取消点 例如:sleep,wait,waitpid,waitid,send等函数 */
pthread_join(todd,(void**)0);
pthread_join(teven,(void**)0);
pthread_mutex_destroy(&m);
return 0;
}
上述代码在线程运行todd函数的时候由于在sleep处退出了,而此时退出并没有把锁打开,这样一来运行runeven函数的线程就加不上锁而等待,而主线程此时又在等它退出,从而形成死锁。