一、线程的使用
线程创建
函数原型及头文件
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
参数:
- tidp:当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。
- attr:一般设为NULL。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
(*start_rtn)(void *):
新创建的线程的入口地址。新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。- arg:向start_rtn函数传递的参数。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。(指针,输入方式是地址,同时要注意是void *型)
返回值:若成功返回0,否则返回错误编号。
线程退出
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消。
3)线程调用pthread_exit。
函数原型及头文件
#include <pthread.h>
int pthread_exit(void *rval_ptr);
参数:rval_ptr:一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
线程等待
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
函数原型及头文件
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
参数:
- thread:线程名
- **rval_ptr:线程的返回值。如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
返回值:若成功返回0,否则返回错误编号。
线程脱离
函数原型及头文件
#include <pthread.h>
int pthread_detach(pthread_t thread);
参数解读
一个线程或者是可汇合(joinable,默认值),或者是脱离的(detached)。当一个可汇合的线程终止时,它的线程ID和退出状态将留存到另一个线程对它调用pthread_join。脱离的线程却像守护进程,当它们终止时,所有相关的资源都被释放,我们不能等待它们终止。如果一个线程需要知道另一线程什么时候终止,那就最好保持第二个线程的可汇合状态。
pthread_detach函数把指定的线程转变为脱离状态。
返回值:若成功返回0,否则返回错误编号。
线程ID获取
函数原型及头文件
#include <pthread.h>
pthread_t pthread_self(void);
返回值:调用线程的ID。
示例代码使用
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void* arg)
{
int num = *(int*)arg;
printf("num = %d\n",num);
printf("这就是子线程\n");
sleep(3);
pthread_exit("hello");
}
int main()
{
pthread_t tid;
int arg = 10;
void* retv;
int ret = pthread_create(&tid, NULL, thread_fun, (void*)&arg);
if(ret == 0)
{
printf("create succes\n");
}
pthread_join(tid, &retv);
printf("子线程结束了 %s\n",(char*)retv);
while(1)
{
}
return 0;
}
/* 传输数字 */
static int ret = 10;//传出数据前面一定要加static!!!
pthread_exit((void *)&ret);
int *pret;//用于存放线程退出时传出的数据
pthread_join(t1,(void **)&pret);//等待t1线程退出
printf("t1 quit ret=%d\n",*pret);//打印t1线程退出时传出的数据
运行结果:
线程的取消
线程取消不是想取消就取消,必须有取消点才可以,线程的取消点主要是阻塞的系统调用
比如主线程需要取消子线程,执行完pthread_cancel(pid)后,子线程并不会被立马取消,而是会等待到阻塞的时候才会被取消,比如sleep的时候。
函数原型及头文件
#include <pthread.h>
int pthread_cancel(pthread_t thread);
参数:thread:线程pid号
返回值:成功返回0,不成功返回错误码
手动创建一个取消点
函数原型及头文件
#include <pthread.h>
void pthread_testcancel(void);
测试代码:
void *thread_fun(void* arg)
{
while(1)
{
pthread_testcancel();//手动设置一个取消点
}
}
int main()
{
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, thread_fun, NULL);
if(ret == 0)
{
printf("create succes\n");
}
sleep(3);
pthread_cancel(tid);//取消线程
while(1)
{
}
return 0;
}
运行:
如果没有手动取消点:
设置取消状态
设置线程的状态,可设置为不可取消或可取消。取消点可取消需要建立在线程的取消状态为可取消
函数原型及头文件
#include <pthread.h>
int pthread_setcancelstate(int state, int* oldstate);
参数:
- state:取消状态,PTHREAD_CANCEL_ENABLE,PTHREAD_CANCEL_DISABLE
- oldstate:旧的状态
返回值:成功返回0,不成功返回错误码。
代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void* arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);//设置线程取消状态为不可取消
while(1)
{
pthread_testcancel();
}
}
int main()
{
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, thread_fun, NULL);
if(ret == 0)
{
printf("create succes\n");
}
sleep(3);
pthread_cancel(tid);//取消子线程
while(1)
{
}
return 0;
}
运行: 5s后更改状态为可取消:
设置取消类型
设置线程的取消类型,默认为有取消点才可取消,同时可设置为可以立马取消。
函数原型及头文件
#include <pthread.h>
int pthread_setcanceltype(int type, int* oldtype);
参数:
- type:要设置的取消类型, PTHREAD_CANCEL_DEFERRED, (需要有取消点) PTHREAD_CANCEL_ASYNCHRONOUS
- oldtype:旧的取消类型
返回值:成功返回0,不成功返回错误码。
代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void *thread_fun(void* arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);//设置为不需要取消点即可取消线程
while(1)
{
//pthread_testcancel();
}
}
int main()
{
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, thread_fun, NULL);
if(ret == 0)
{
printf("create succes\n");
}
sleep(3);
pthread_cancel(tid);
while(1)
{
}
return 0;
}
运行:
线程的清理
子线程运行时,申请了一块内存还没内有释放的时候,被主线程取消了,这就会造成内存泄漏,这是不好的,所以我们需要进行线程的清理,这是很有必要的!!!
函数原型及头文件
#include <pthread.h>
void pthread_cleanup_push(void(*routine)(void*), void* arg);
void pthread_cleanup_pop(int execute);
注意:这俩个函数必须成对使用,不然编译会出错!
参数:
- routine:执行函数
- arg:执行函数的参数
- execute:0-不执行pthread_cleanup_push 非0-执行pthread_cleanup_push
函数使用tip:pthread_cleanup_push函数被触发后会跳转到routine函数中执行,pthread_cleanup_push会被触发的条件:
- 被pthread_cancel取消掉(必须建立不可0执行pthread_cleanup_pop函数)
- 执行pthread_exit(必须建立不可0执行pthread_cleanup_pop函数)
- 非0执行pthread_cleanup_pop函数,无需被取消或exit退出线程
- return不可触发
上面的条件需要自己缕清一下思路,如果不清楚就可以看看下面的实例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void clean_func(void* arg)
{
printf("执行清理工作\n");
}
void *thread_fun(void* arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_cleanup_push(clean_func, NULL);
pthread_cleanup_pop(0);
//参数0执行了pthread_cleanup_pop,所以后面的pthread_exit根本触发不了pthread_cleanup_push
pthread_exit(0);
while(1)
{
}
}
int main()
{
pthread_t tid;
int arg = 10;
int ret = pthread_create(&tid, NULL, thread_fun, NULL);
if(ret == 0)
{
printf("create succes\n");
}
sleep(3);
while(1)
{
}
return 0;
}
运行:
稍微修改一下子线程函数:
void *thread_fun(void* arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_cleanup_push(clean_func, NULL);
pthread_exit(0);
//线程根本运行不到这里,因为前面已经exit了,这里完全是为了满足成对使用编译不出错
pthread_cleanup_pop(0);
while(1)
{
}
}
运行:
再修改一下子线程代码,实现条件3:
void *thread_fun(void* arg)
{
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
pthread_cleanup_push(clean_func, NULL);
//非0执行pthread_cleanup_pop函数,无需被取消或exit退出线程即可触发
pthread_cleanup_pop(1);
while(1)
{
}
}
运行:
二、互斥锁的使用
互斥锁的初始化
函数原型及头文件
#include <pthread.h>
int pthread_mutex_init(phtread_mutex_t *mutex, const pthread_mutexattr_t *restrict attr);
参数:
- mutex:互斥量指针
- restrict attr:互斥量属性,一般为NULL缺省属性
返回值:成功返回0,不成功返回错误码
互斥量加锁/解锁
函数原型及头文件
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
参数:mutex:互斥量指针
返回值:成功返回0
lock 函数与 unlock 函数分别为加锁解锁函数,只需要传入已经初始化好的 pthread_mutex_t 互斥量指针。成功后会返回 0 。当某一个线程获得了执行权后,执行 lock 函数,一旦加锁成功后,其余线程遇到 lock 函数时候会发生阻塞,直至获取资源的线程执行 unlock 函数后。unlock 函数会唤醒其他正在等待互斥量的线程
销毁互斥量
函数原型及头文件
#include <pthread.h>
int pthread_mutex_destory(pthread_mutex_t *mutex);
参数:mutex:互斥量指针
返回值:成功返回0
三、条件变量使用
创建和销毁条件变量
函数原型及头文件
#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
//cond_attr 通常为 NULL
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
//这些函数成功时都返回 0
参数:
- cond:条件变量指针
- cond_attr:条件变量属性,一般为NULL缺省属性
返回值:成功返回0,不成功返回错误码
等待条件变量
函数原型及头文件
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, cond struct timespec *restrict timeout);
参数:
- cond:条件变量指针
- mutex:互斥量指针
返回值:成功返回0,不成功返回错误码
pthread_cond_wait函数行为如下:
- 解锁互斥锁:调用 pthread_cond_wait 的线程首先会释放(解锁)它当前持有的互斥锁。这一步是必要的,因为条件变量通常与互斥锁一起使用,以确保对共享数据的访问是同步的。解锁互斥锁允许其他线程获取该锁,从而可以安全地修改共享数据。
- 加入等待队列:在解锁互斥锁之后,调用 pthread_cond_wait 的线程会将自己添加到与该条件变量相关联的等待队列中。此时,线程进入阻塞状态,等待被唤醒。
阻塞并等待信号:线程在等待队列中保持阻塞状态,直到它收到一个针对该条件变量的信号(通过 pthread_cond_signal 或 pthread_cond_broadcast 发出)。需要注意的是,仅仅因为线程在等待队列中并不意味着它会立即收到信号;它必须等待直到有其他线程显式地发出信号
- 重新获取互斥锁:当线程收到信号并准备从 pthread_cond_wait 返回时,它首先会尝试重新获取之前释放的互斥锁。如果此时锁被其他线程持有,那么该线程会阻塞在互斥锁的等待队列中,直到获得锁为止。这一步确保了线程在继续执行之前能够重新获得对共享数据的独占访问权。
检查条件:一旦线程成功获取到互斥锁,它会再次检查导致它调用 pthread_cond_wait 的条件是否现在满足。虽然通常认为在收到信号时条件已经满足,但这是一个编程错误的常见来源。正确的做法是在每次从 pthread_cond_wait 返回后都重新检查条件,因为可能有多个线程在等待相同的条件,或者条件可能在信号发出和线程被唤醒之间发生变化
- 返回并继续执行:如果条件满足,线程会从 pthread_cond_wait 返回,并继续执行后续的代码。如果条件仍然不满足,线程可以选择再次调用 pthread_cond_wait 进入等待状态,或者执行其他操作。
简单点描述就是需要调用通知函数的线程调用通知函数并且释放互斥量。等待函数才能顺利成功返回
使用需要配合互斥量使用:
pthread_mutex_lock(&g_tMutex);
// 如果条件不满足则,会 unlock g_tMutex
// 条件满足后被唤醒,会 lock g_tMutex
pthread_cond_wait(&g_tConVar, &g_tMutex);
/* 操作临界资源 */
pthread_mutex_unlock(&g_tMutex);
这里的条件不满足表示wait函数没有被唤醒,这涉及到下一个函数,先看上面的代码:线程先lock(持有互斥锁),这时其他线程如果也想获得这个锁就会被阻塞,该线程代码继续执行到pthread_cond_wait 函数时,该线程会释放锁,并进入阻塞状态等待被其他线程唤醒;等其他线程调用唤醒函数后,该线程才会执行wait函数之后的代码,此时该线程持有互斥锁,最后该线程主动释放互斥锁。
通知条件变量
函数原型及头文件
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal函数将唤醒等待该条件的某个线程,而pthread_cond_broadcast函数将唤醒等待该条件的所有进程。
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
参数:
- cond:条件变量指针
返回值:成功返回0,不成功返回错误码