目录
二.如何创建并运行一个线程:pthread_create 函数
七.线程取消运行函数:pthread_cancel /pthread_testcancel /pthread_setcancelstate /pthread_setcanceltype
一.线程
线程的概念:
线程是进程的最小组成单位:一个进程里面,至少有一个线程的存在,称为主线程,main函数运行的就是所谓的主线程
多线程能做什么?
在学习多线程之前,一个进程只能运行一个循环,在学习了多线程之后,一个进程就能同时运行多个循环。想要同时运行n个循环的话,只要让进程里面拥有n个线程即可。
进程也是进程当中最小的代码执行单位,有一个线程,就能同时执行一份代码,有n个线程,就能同时执行n份代码 “进程是资源分配的最小单位,线程是CPU调度的最小单位”
二.如何创建并运行一个线程:pthread_create 函数
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
功能描述:创建一个线程 参数 thread:pthread_create函数创建出线程之后,会同步的产生一个对应的线程id号,然后将线程id保存到thread指向的内存里面
参数 attr:线程属性的意思,大多数情况下,直接传0,表示使用默认属性
参数 start_routine:pthread_create函数创建出线程线程之后,该线程运行代码就是 start_routine指针指向的函数所运行的代码
参数 arg:其实是传递给 start_routine 函数的参数 返回值:成功返回0,失败返回非0 注意:线程所在的库,在编译的链接阶段不会自动链接,所以需要我们手动的链接,在编译的最后,加上语句 -lpthread
三.线程和进程的区别
1)内存管理有区别
父进程创建子进程之后,子进程完全拷贝父进程的内存空间。也就是说子进程和父进程之间,内存是独立的,互不干涉
主线程创建子线程之后,子线程只会额外的开销8k的内存空间,这8k内存用存放线程属性,内存的起始位置,终止位置,线程 id等等线程相关的数据
所以,子线程的内存空间,和主线程之间是共享的:主线程和子线程之间,想要访问同一个数据的话,最方便的形式就是全局变量
2)进程是资源的获取单位,线程是资源的分配运行单位
3)基于第二点,多进程之间的切换,切换效率要低于多线程之间的切换
无论是多进程还是多线程之间的切换,切换的形式叫做:时间片轮询,上下文切换
4)多进程之间的运行互不影响:父进程结束运行,不影响子进程的运行。同理子进程结束运行,也不影响父进程的运行但是多线程之间,他们的运行不是完全独立:主线程结束运行,所有线程都得结束运行(因为主线程是主函数,主函数结束了,进程就结束了)。其他线程结束运行,互相之间不影响。
四. 线程名词的解释
1 临界数据:能够被多个线程所访问的数据,就是临界数据
2 临界区域:能够被多个线程所执行的代码,我们称为临界区域
3 互斥与同步:
互斥:当一个临界数据,正在被多个线程所访问的时候,线程与线程之间的关系,也应该保持互斥
同一时间,只有1个线程能够访问临界数据,在所有的访问逻辑完成之前,别的线程不应该访问该临界数据
同步:多个线程,可预测的,又先后顺序的,去访问临界数据
五. 线程资源回收函数:
int pthread_join(pthread_t thread, void **retval);
功能描述:指定回收thread线程的资源,并使用 retval 这段内存(该内存中保存的是一个void*指针)用来接受线程结束运行时候的运行状态(也就是线程入口函数 start_routine 的返回值) pthread_join 运行模式 和 wait 是一样的:
1:都是阻塞型函数,不回收到资源,不会解除阻塞的
2:都会去接受被回收对象的结束运行时候的状态 pthread_join 和 wait也是一样的问题:主线程在忙于自己的逻辑,执行不到pthread_join,但是线程有成熟的解决访问 ① 主线程自己本身没活干,这个时候直接pthread_join 就行了 ② 如果主线程自己有活干,怎么办?将线程的属性,设置成分离式属性,分离式属性的作用就是:当线程结束运行之后,所有线程的8k资源,自动回收
如何将一个线程设置成分离式线程:pthread_detach
int pthread_detach(pthread_t thread); 功能描述:传入线程id号,将该线程设置成分离式属性
六.线程退出函数:pthread_exit
void pthread_exit(void *retval); 功能描述:结束当前线程,并且将参数retval作为线程入口函数的返回值,向外返回
七.线程取消运行函数:pthread_cancel /pthread_testcancel /pthread_setcancelstate /pthread_setcanceltype
1.)int pthread_cancel(pthread_t thread);
功能描述:取消线程id为thread的线程的运行 该函数和pthread_exit对比,优势在于:pthread_cancel只要能够获取目标线程的id号,能够在进程的任意部分结束该线程的运行。而pthread_exit只能在线程内部,结束自身的运行 注意:pthread_cancel调用之后,只是向目标线程发送取消运行的信号 至于,接收到取消运行信号的线程,默认操作下,是不会立刻取消运行的,而是运行到下一个退出点之后,才会取消运行。至于这个默认的退出点在哪,不一定 但是,我们可以手动的去设置退出点,线程运行到手动设置的退出点后,肯定会退出运行 怎么手动设置退出点:
2.)void pthread_testcancel(void);
功能描述:手动设置线程的退出点 当然,我们也可以将线程设置成调用cancel之后立刻退出
3.)int pthread_setcancelstate(int state, int *oldstate);
功能描述:用来设置线程取消状态,是能够使用cancel函数取消运行,还是不能使用cancel函数取消运行 参数 state,有2个选择 PTHREAD_CANCEL_ENABLE 默认操作:运行使用cancel取消线程的运行 PTHREAD_CANCEL_DISABLE 不允许使用cancel取消线程的运行
4.)int pthread_setcanceltype(int type, int *oldtype);
功能描述:设置线程cancel的退出类型,是立刻退出还是运行到下一个退出点后才退出 参数type:类型,有2个选择 PTHREAD_CANCEL_DEFERRED: 默认设置,线程取消后,运行到下一个退出点才取消 PTHREAD_CANCEL_ASYNCHRONOUS: 现在在被cancel之后,立刻取消运行,但是系统不保证一定会立刻取消 参数oldtype:用来接受原先线程取消类型,传0表示不接受
如何在线程内部获取线程自身的id号 调用函数 pthread_self 即可,通过返回就能获取
八.练习(个人练习)
在一个进程中,创建一个子线程。 主线程负责:向文件中写入数据 子线程负责:从文件中读取数据 要求使用线程的同步逻辑,保证一定在主线程向文件中写入数据成功之后,子线程才开始运行,去读取文件中的数据
#include <myhead.h>
#include <strings.h>
// 在一个进程中,创建一个子线程。
// 主线程负责:向文件中写入数据
// 子线程负责:从文件中读取数据
// 要求使用线程的同步逻辑,
// 保证一定在主线程向文件中写入数据成功之后,
// 子线程才开始运行,去读取文件中的数据
//
int s = 0; // 临界资源·
void waitres(int *t)
{
while (*t <= 0)
; // 如果s小于0则锁
*t = *t - 1;
}
void signres(int *t)
{
*t = *t + 1; // 加上资源
}
void *readfile(void *arg)
{
waitres(&s); // 检查自己能不能读
int file = open("06text.txt", O_RDONLY);
if (file == -1)
{
perror("openerr");
return 0;
}
char buf[100] = {0};
int retval = 0;
while ((retval = read(file, buf, 100)) != 0 && retval != -1)
{
printf("%s\n", buf);
bzero(buf, retval);
}
close(file);
}
int main(int argc, const char *argv[])
{
pthread_t id;
if (pthread_create(&id, 0, readfile, 0) != 0)
{
perror("pthread_create");
return 1;
}
int file = open("06text.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
char buf[6] = {"hello"};
if (write(file, buf, 5) > 0)
{
signres(&s); // 允许子进程读
}
close(file);
return 0;
}