线程课堂笔记

概念

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程是任务调度和执行的基本单位

线程的特点

线程具有的独立属性

线程是进程的一个执行单元,是进程内的调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程,每个线程有独立的切换状态,调度优先级,有自己独立的函数栈,自己独立的错误号,每一个线程有自己独立的信号屏蔽字和未决信号集,每个线程有自己独立的tack_struct结构体

线程不安全,不稳定,不健壮;

线程是进程的一个可调度的实体,线程间协同并共享同一个进程的资源,一个线程的崩溃将导致整个应用软件的崩溃。一个线程只隶属于一个进程,不同进程的线程间只有通过信息机制才能通讯。

线程多线程的开销很低;(实质是函数的切换)

使用来实现多线任务时,由于线程本质上它只是程序(进程)的一个函数,只不过线程函数,与普通函数的区别是,普通函数时单线的运行关系,而线程函数被注册为线程后,是多线并发运行,对于普通函数来说,只有相互调动时才会涉及函数间的切换,但是对于线程函数来说,只要运行时间片到了就会切换,但是不管是那种函数间的切换,进程自己函数的切换只是进程内部的事情,不涉及进程间切换, 就省去了进程间切换巨大开销。

线程通信机制简单(主要使用全局变量)

线程两种状态

可结合态:这种状态下的线程是能够被其他进程回收其资源或杀死的

分离态:这种状态下的线程是不能够被其他线程回收或杀死的;它的存储资源在它终止时由系统自动释放

默认情况下,线程被创建成可结合态

 线程的操作

线程函数不是由操作系统提供,而是由线程库提供(libpthread.a/.so);

原本线程函数也可以完全由OS来实现,但是后来为了不给OS增加负担,同时也为了提高线程的灵活性,后来的线程就不在由OS提供,而是由单独的线程库来提供,不过线程库在实现时,也是调用了相应的系统API的,也就是说线程的核心实现也是离不开OS支持的。

线程创建函数:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

第一个参数:线程id (传出参数),存放线程的TID;

第二个参数:线程的属性(优先级、信号屏蔽字) 一般置为NULL;

第三个参数:线程需要执行的函数(函数指针);

函数类型为void *(*) (void *),pthread_create它会把这个函数注册为线程,如果不注册,线程函数就是一个普通的函数。线程函数需要我们自己定义,比如:

void *pth_fun(void *pth_arg)

{

...//线程要做的事情

}

pth_fun和pth_arg的命名由自己决定。

第四个参数:给线程函数传参的;(一般设置为NULL,除非有需要传入的参数)

传递给线程函数的参数,这个参数会传递给pth_arg,如果参数很多的话,我们做成一个结构体,然后把结构体变量的地址传过去。如果你不想传递参数的话,你可以设置为NULL。

功能:把void *(*start_routine) (void *)函数注册为一个线程函数,该函数一旦注册成功,这个函数就以次线程的方式开始并发运行起来,如果不注册的话,这个函数就是一个普通函数。凡是使用pthread_create注册的线程,都是次线程,次线程会和主线程一起并发运行。

返回值:成功返回0,失败返回非零错误号。

 

 

线程退出:

被动退出:int pthread_cancel(pthread_t thread);

功能:当次线程是死循环时,可以调动这个函数主动取消该线程。

返回值:成功返回0,失败返回非零错误号。

参数:thread(要取消线程的TID)

主动退出:void pthread_exit(void *retval);

功能: 线程调用这个函数时,可以主动退出(终止)。这类似于exit函数,不过exit是终止整个进程的,而pthread_exit是终止次线程的。如果你在次线程里面调用错误,调用的是exit,整个进程就终止了。

返回值:成功返回0,失败返回非零错误号。

参数:retval:线程结束时的返回值。

如果返回值很多时,就封装成一个结构体,返回结构体变量的地址即可

 

注册线程退出处理函数:

void pthread_cleanup_push(void (*routine)(void *), void *arg);(压栈)

将类型为void (*routine)(void *)函数注册为“线程退出处理函数”,arg为传递给退出处理函数的参数。注册的原理就是将处理函数地址压入线程栈。我们可以反复调用该函数注册多个退出处理函数,但是一般一个就够了。

void pthread_cleanup_pop(int execute);(弹栈)

如果参数写!0:会将压入栈中的推出处理函数地址弹出,然后调用退出函数进行线程的扫尾处理。

如果参数写0:不弹出调用

如果注册了多个线程退出处理函数的话,由于栈先进后出的特点,所以注册压栈的顺序与弹栈调动的顺序刚好相反。

这个函数必须和pthread_cleanup_push配对出现,有一个pthread_cleanup_push,就必须要对应有一个pthread_cleanup_pop,就算这个函数调用不到也必须写,否者编译时不通过

注意:调用thread_cleanup_pop(!0),主动弹栈(不能再if while语句中出现)

如果线程是调用pthread_cancel取消的,也会弹栈

如果线程是调用pthread_exit函数退出的,也会弹栈

return退出的话,是不会自动弹栈的,要弹栈的话,必须主动调动 thread_cleanup_pop(!0)

线程等待

目的:明确线程的退出顺序,保证一个线程退出并且回收资源后允许下一个进程退出,保证当前线程退出后,创建的新线程不会复用刚才退出线程的地址空间,获得新线程退出时的结果是否正确的退出返回值,保证不会发生内存泄露等问题

函数:int pthread_join(pthread_t thread, void **retval);

 

阻塞等待tid为thread的次线程结束,结束时该函数会回收次线程所占用的所有资源(存储空间)这个函数只对次线程有意义,对主线程没有意义,因为主线程结束时真个进程就结束了,整个进程资源会由父进程回收。这个函数一般都是由主线程调用,以回收相关次线程的资源,当然次线程也是可以调用这个函数来回收其它次线程资源的。

返回值: 成功返回0,失败返回错误号。

参数:

thread:指定要回收次线程的TID

retval:次线程函数返回的返回值(一般设为NULL)

线程资源不采用进程退出之后一起回收的原因:

有些程序(进程)一旦运行后将会长期运行,不会结束,所以次线程在结束时必须回收资源,如果不回收,每结束一个次线程就导致一部分资源被占用,慢慢累积会使得整个进程资源越用越少,最好导致进程崩溃,所以次线程结束时,必须回收次线程资源。

避免多线程退出导致的内存泄漏方法:

每个可结合线程需要显示的调用pthread_join回收,或将结合态变为分离态

设置分离态方法:

线程分离函数pthread_detach;

int pthread_detach(pthread_t thread);

参数:thread:你要分离的那个次线程的TID。

这个函数的功能就是分离次线程,让次线程在结束时自动回收资源,如果次线程的资源不希望别人调用pthread_join函数来回收的话,而是希望自己在结束时,自动回收资源的话,就可以调用这个函数。

返回值: 成功返回0,失败返回错误号。

修改属性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

作用:设置线程属性,分离or非分离 成功:0;失败:错误号

int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);

作用:获取程属性,分离or非分离 成功:0;失败:错误号

参数attr必须为已初始化的线程属性

detachstate:PTHREAD_CREATE_DETACHED(分离线程)、PTHREAD _CREATE_JOINABLE(非分离线程)。

注意:如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。

线程同步

互斥锁

步骤:定义一个互斥锁(变量)

pthread_mutex_t mutex;

初始化互斥锁:预设互斥锁的初始值

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

功能:初始化定义的互斥锁

返回值:总是返回0,所以这个函数不需要进行出错处理。

参数:mutex:互斥锁,需要我们自己定义。

比如:pthread_mutex_t mutex;

pthread_mutex_t是一个结构体类型,所以mutex实际上是一个结构体变量。

attr:互斥锁的属性

设置NULL表示使用默认属性,除非我们想要实现一些互斥锁的特殊功能,否则默认属性就够用了。

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER 编译时初始化锁位解锁状态

加锁解锁

pthread_mutex_lock(&mutex)(阻塞加锁)访问临界区加锁操作

pthread_mutex_trylock( &mutex)(非阻塞加锁);

pthread_mutex_lock() 类似,不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待。

pthread_mutex_unlock(&mutex): 访问临界区解锁操

进程退出时销毁互斥锁

pthread_mutex_destroy

函数原型

int pthread_mutex_destroy(pthread_mutex_t *mutex);

功能:销毁互斥锁

删除互斥锁相关的数据,释放互斥锁数据所占用的各种内存资源。

返回值:成功返回0,失败返回非零错误号

线程信号量

使用步骤:定义信号量集合

信号量集合需要我们自己定义,

比如:sem_t sem[2],

线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。

sem[0]:第一个信号量

sem[1]:第二个信号量

B、 初始化集合中的每个信号量

初始化信号量的函数

函数原型

int sem_init(sem_t *sem, int pshared, unsigned int value);

功能:初始化线程信号量集合中的某个信号量,给它设置一个初始值。

返回值:成功返回0,失败返回-1,errno被设置。

注意信号量的错误号不是返回的,而是设置到errno中。

参数:sem:信号量集合中的某个信号量

sem_init(&sem[0], int pshared, unsigned int value);

线程信号量集合其实就是自定义的一个数组,而进程信号量集合则是通过semget函数创建的。

我们只要把数组定义为全局变量,所有的线程即可共享使用,不像进程信号量,需要semid才能实现共享操作。

- pshared:

+ 0:给线程使用

+ !0:可以给进程使用

不过对于进程来说,我们更多的还是使用进程信号量,因为线程信号量用到进程上时,存在一些不稳定的情况。

- value:初始化值

对于二值信号量来说,要么是1,要么是0。

P操作:

函数原型:

int sem_wait(sem_t *sem);//阻塞p操作

功能:阻塞p操作集合中某个信号量,值-1

如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止。

返回值:成功返回0,失败返回-1,errno被设置。

参数:p操作的某个信号量。

比如:sem_wait(&sem[0]);

sem_wait的兄弟函数

int sem_trywait(sem_t *sem):不阻塞

如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有p操作成功就是出错返回,不再阻塞。

v操作

函数原型

int sem_post(sem_t *sem);

功能:对某个信号量进行v操作,v操作不存在阻塞问题。

v操作成功后,信号量的值会+1

返回值:成功返回0,失败返回-1,errno被设置。

删除信号量

函数原型

int sem_destroy(sem_t *sem);

删除某个信号量,把所有信号量都删除后,信号量集合就被销毁,这与删除进程信号量集合有所不同,对于进程信号量集合来说,只要删除一个信号量,整个集合即被删除,但是对于线程信号量来说,需要一个一个的删除,当所有信号量都删除完后,集合才被删除完毕。

条件变量

作用(线程协同)

多线程配合工作时,当线程检测到某条件不满足时就休眠,直到别的线程将条件准备好,然后通过条件变量将其唤醒。

使用步骤:

定义一个条件变量(全局变量)由于条件变量需要互斥锁的配合,所以还需要定义一个线程互斥锁。

初始化条件变量

函数原型:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);

功能:初始化条件变量,与互斥锁的初始化类似。

pthread_cond_t cond; //定义条件变量

pthread_cond_init(&cond, NULL); //第二个参数为NULL,表示不设置条件变量的属性。

也可以直接初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//与互斥锁的初始化的原理是一样的

返回值:成功返回0,失败返回非零错误号

参数:

- cond:条件变量

- attr:用于设置条件变量的属性,设置为NULL,表示使用默认属性

使用条件变量

函数原型

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

功能:检测条件变量cond,如果cond没有被设置,表示条件还不满足,别人还没有对cond进行设置,此时

pthread_cond_wait会阻塞直到别的线程设置cond表示条件准备好后,才会被唤醒。

使用条件变量

参数:cond:条件变量

mutex:和条件变量配合使用的互斥锁

pthread_cond_wait的兄弟函数

int pthread_cond_timedwait(pthread_cond_t *restrict cond, \ pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

多了第三个参数,用于设置阻塞时间,如果条件不满足时休眠,但是不会一直休眠,

当时间超时后,如果cond还没有被设置,函数不再休眠。

 

 

 

 

设置条件变量的函数

函数原型

int pthread_cond_signal(pthread_cond_t *cond);

功能:当线程将某个数据准备好时,就可以调用该函数去设置cond,表示条件准备好了, pthread_cond_wait检测到cond被设置后就不再休眠(被唤醒),线程继续运行,使用别的线程准备好的数据来做事。

当调用pthread_cond_wait函数等待条件满足的线程只有一个时,就是用pthread_cond_signal来唤醒,如果说有好多线程都调用pthread_cond_wait在等待时,

int pthread_cond_broadcast(pthread_cond_t *cond);

删除条件变量,也需要把互斥锁删除。

int pthread_cond_destroy(pthread_cond_t *cond);

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值