本章是线程的取消 、清理,互斥和同步,以及互斥锁、读写锁和死锁的内容
线程取消不是 “立刻杀死”,而是一种协作式机制:
pthread_cancel(tid)
发送 “取消请求”pthread_testcancel()
)才会响应pthread_exit(PTHREAD_CANCELED)
,主线程 pthread_join
能拿到这个特殊返回值函数 | 作用 | 核心细节 |
---|---|---|
pthread_cancel(tid) |
发取消请求 | 只是 “通知”,不保证立刻执行 |
pthread_testcancel() |
手动取消点 | 主动检查是否有取消请求,有则响应 |
pthread_setcancelstate() |
控制 “是否允许取消” | 可临时禁用 / 启用取消响应 |
pthread_setcanceltype() |
控制 “何时响应取消” | 延迟响应(DEFERRED )或立即响应(ASYNCHRONOUS ) |
#include
#include
#include
void *func(void *arg) {
printf("Child thread start\n");
while (1) {
// sleep(5) 是系统调用,属于“取消点”
sleep(5);
}
// 永远执行不到这里
pthread_exit("thread return");
}
int main() {
pthread_t tid;
void *retv;
pthread_create(&tid, NULL, func, NULL);
sleep(5); // 等子线程进入循环
pthread_cancel(tid); // 发取消请求
pthread_join(tid, &retv);
// 子线程被取消后,retv 会是 PTHREAD_CANCELED
printf("Thread return: %s\n", (retv == PTHREAD_CANCELED) ? "PTHREAD_CANCELED" : (char*)retv);
return 0;
}
运行逻辑:
sleep(5)
(系统调用 = 取消点),收到 pthread_cancel
后会终止pthread_join
拿到 PTHREAD_CANCELED
,输出类似:Thread return: PTHREAD_CANCELED
void *func(void *arg) {
printf("Child thread start\n");
while (1) {
// 手动插入取消点,检查是否有取消请求
pthread_testcancel();
// 模拟耗时操作(无系统调用,原本不会响应取消)
sleep(1);
}
pthread_exit("thread return");
}
// main 函数逻辑和之前一样...
关键:pthread_testcancel()
让 “非系统调用” 的循环也能响应取消请求
void *func(void *arg) {
printf("Child thread start\n");
// 禁用取消响应:即使收到 pthread_cancel 也不处理
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
{
// 这里即使有取消请求,也不会响应
sleep(5);
// 手动检查(但因为禁用了,检查也没用)
pthread_testcancel();
}
// 重新允许取消响应
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
while (1) {
sleep(1);
}
pthread_exit("thread return");
}
效果:
PTHREAD_CANCEL_DISABLE
区间内,pthread_cancel
发的请求会被 “暂时屏蔽”void *func(void *arg) {
printf("Child thread start\n");
// 设置“延迟响应”(默认就是这个,可显式设置)
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
{
// 遇到取消点(如 sleep)才会响应
sleep(5);
}
// 设置“立即响应”
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
{
// 无需等取消点,收到请求会立刻终止
sleep(5);
}
pthread_exit("thread return");
}
区别:
DEFERRED
(延迟):必须等 “取消点”(系统调用 / pthread_testcancel
)才响应ASYNCHRONOUS
(立即):理论上随时可能终止(但实际受系统实现限制,极端场景才会明显)pthread_exit
和pthread_cancel
会触发清理函数,return
不会void pthread_cleanup_push(void (*routine)(void *), void *arg);
// routine:清理函数,arg:传递给清理函数的参数
void pthread_cleanup_pop(int execute);
// execute=1:执行清理函数;execute=0:不执行
#include
#include
#include
void cleanup(void *arg) {
printf("cleanup, arg=%s\n", (char *)arg);
}
void cleanup2(void *arg) {
printf("cleanup2, arg=%s\n", (char *)arg);
}
void *func(void *arg) {
printf("This is child thread\n");
pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);//立即取消
// 注册清理函数,成对出现
pthread_cleanup_push(cleanup, "abcd");
pthread_cleanup_push(cleanup2, "efgh");
{
sleep(1);
}
while (1) {
printf("sleep\n");
sleep(1);
}
// 实际因死循环到不了这里,只是演示成对写
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_exit("thread return");
}
int main() {
pthread_t tid;
void *retv;
pthread_create(&tid, NULL, func, NULL);
sleep(3); // 等待线程运行起来
pthread_cancel(tid); // 发起取消,触发清理函数
pthread_join(tid, &retv);
while (1) {
sleep(1);
}
return 0;
}
This is child thread
sleep
sleep
cleanup2, arg=efgh
cleanup, arg=abcd
pthread_cleanup_push
与pthread_cleanup_pop
必须成对出现return
结束时,清理函数不会执行atomic
类型)// 静态初始化(适用于全局/静态变量)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 动态初始化(运行时创建,需手动销毁)
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁(阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁(非阻塞)
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁
//两个线程同时写一个文件
#include
#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//静态方式
FILE *fp;
void *func2(void *arg){
pthread_detach(pthread_self());
printf("This func2 thread\n");
char str[]="I write func2 line\n";
char c;
int i=0;
while(1){//一个字符一个字符的写
pthread_mutex_lock(&mutex);
while(i
pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_rdlock(&rwlock); // 读加锁(阻塞)
pthread_rwlock_tryrdlock(&rwlock); // 读加锁(非阻塞)
pthread_rwlock_wrlock(&rwlock); // 写加锁(阻塞)
pthread_rwlock_trywrlock(&rwlock); // 写加锁(非阻塞)
pthread_rwlock_unlock(&rwlock); // 解锁
#include
#include
#include
#include
pthread_rwlock_t rwlock; //定义一个结构体
FILE *fp;
void * read_func(void *arg){
pthread_detach(pthread_self());
printf("read thread\n");
char buf[32]={0};
while(1){
pthread_rwlock_rdlock(&rwlock);//读锁
while(fgets(buf,32,fp)!=NULL){
printf("%d,rd=%s\n",(int)arg,buf);
usleep(1000);
}
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
void *func2(void *arg){
pthread_detach(pthread_self());
printf("This func2 thread\n");
char str[]="I write func2 line\n";
char c;
int i=0;
while(1){
pthread_rwlock_wrlock(&rwlock);
while(i
运行结果
read thread
read thread
This is func1 thread
This func2 thread
1,rd=I write func2 line
2,rd=I write func2 line
1,rd=You read func1 thread
2,rd=You read func1 thread
1,rd=I write func2 line
2,rd=I write func2 line
...
#include
#include
#include
#include
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
FILE *fp;
void *func2(void *arg){
pthread_detach(pthread_self());
printf("This func2 thread\n");
char str[]="I write func2 line\n";
char c;
int i=0;
while(1){
pthread_mutex_lock(&mutex);
printf("%d,I got lock2\n",(int)arg);
sleep(1);
pthread_mutex_lock(&mutex2);
printf("%d,I got 2 locks\n",(int)arg);
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex1);
sleep(10);
}
pthread_exit("func2 exit");
}
void *func(void *arg){
pthread_detach(pthread_self());
printf("This is func1 thread\n");
char str[]="You read func1 thread\n";
char c;
int i=0;
while(1){
pthread_mutex_lock(&mutex);
printf("%d,I got lock1\n",(int)arg);
sleep(1);
pthread_mutex_lock(&mutex2);
printf("%d,I got 2 locks\n",(int)arg);
pthread_mutex_unlock(&mutex2);
pthread_mutex_unlock(&mutex);
sleep(10);
}
pthread_exit("func1 exit");
}
int main(){
pthread_t tid,tid2;
void *retv;
int i;
fp = fopen("1.txt","a+");
if(fp==NULL){
perror("fopen");
return 0;
}
pthread_create(&tid,NULL,func,1);
pthread_create(&tid2,NULL,func2,2);
while(1){
sleep(1);
}
}
死锁触发流程
T0 时刻:
func
获取mutex
,打印 "I got lock1"func2
获取mutex2
,打印 "I got lock2"T1 时刻:
func
尝试获取mutex2
(被func2
持有),进入阻塞func2
尝试获取mutex
(被func
持有),进入阻塞结果:两个线程永久等待对方释放锁,形成死锁