随着多线程编程的日益普及,pthread_cancel也成为了一个比较常用的API。它可以用于取消一个正在运行的线程。然而,如果使用不当,pthread_cancel可能会导致一些问题。本文将介绍如何使用pthread_cancel以及避免导致线程取消问题。
一、如何使用pthread_cancel
pthread_cancel的原型如下:
```c
#include
int pthread_cancel(pthread_t thread);
```
它用于取消指定线程。传入的参数thread是被取消的线程标识符。pthread_cancel并不会立即终止线程的执行,而是向线程发送一个取消请求。线程如果收到取消请求,会在一次PTHREAD_CANCEL_ENABLE和PTHREAD_CANCEL_DEFERRED的状态检查之后被取消。这两个状态都可以用pthread_setcanceltype函数进行设置。
下面是一个例子,调用pthread_cancel来取消一个线程:
```c
#include
#include
#include
void *thread_func(void *arg)
{
int i = 0;
while (1) {
printf("thread_func: %d\n", i++);
usleep(500000);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
perror("pthread_create error");
return -1;
}
sleep(5);
pthread_cancel(tid);
pthread_join(tid, NULL);
return 0;
}
```
在上面的例子中,主线程创建了一个新线程thread_func,并在调用pthread_cancel取消线程后等待线程结束。
注意,在使用pthread_cancel时要注意线程的取消状态。如果线程被设置为PTHREAD_CANCEL_DISABLE状态,那么调用pthread_cancel是无效的。我们可以使用pthread_setcancelstate来设置线程的取消状态。
二、避免导致线程取消问题
尽管pthread_cancel看起来很方便,但它存在一些问题。下面我们将介绍一些问题及其解决方案。
1. 线程可能被取消在一个不安全的状态下
在使用pthread_cancel时,线程可能会被取消在一个不安全的状态下。考虑下面的例子:
```c
#include
#include
#include
#include
void *thread_func(void *arg)
{
char *buf = (char *)malloc(1024);
while (1) {
printf("thread_func: running\n");
sleep(1);
free(buf);
buf = (char *)malloc(1024);
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
perror("pthread_create error");
return -1;
}
sleep(5);
pthread_cancel(tid);
pthread_join(tid, NULL);
return 0;
}
```
在上面的例子中,线程不停地分配和释放一个1024字节大小的缓冲区。如果一个线程被取消,它可能在free之后,malloc之前被取消,导致缓冲区没有被释放,造成内存泄漏。这是因为pthread_cancel并不能保证在安全的状态下取消线程。为了避免这种情况,我们可以使用pthread_cleanup_push和pthread_cleanup_pop来保证在取消线程前释放内存。修改后的代码如下:
```c
#include
#include
#include
#include
void cleanup(void *arg)
{
free(arg);
}
void *thread_func(void *arg)
{
char *buf = (char *)malloc(1024);
pthread_cleanup_push(cleanup, buf);
while (1) {
printf("thread_func: running\n");
sleep(1);
free(buf);
buf = (char *)malloc(1024);
}
pthread_cleanup_pop(1);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
perror("pthread_create error");
return -1;
}
sleep(5);
pthread_cancel(tid);
pthread_join(tid, NULL);
return 0;
}
```
在上面的代码中,我们使用了pthread_cleanup_push和pthread_cleanup_pop来保证在线程退出之前释放缓冲区。一个被取消的线程会先执行已经注册的pthread_cleanup_pop函数,并在底层恢复到一个安全的状态,然后再执行取消操作。
2. 锁可能被永久占用
在使用多线程编程时,锁是用来保护一段代码或共享数据的关键性机制。如果一个线程取消时正在持有锁,那么这个锁可能会被永久占用。考虑下面的例子:
```c
#include
#include
#include
#include
pthread_mutex_t mutex;
void *thread_func(void *arg)
{
pthread_mutex_lock(&mutex);
printf("thread_func: lock\n");
sleep(10); // 此处加了等待时间以便观察
pthread_mutex_unlock(&mutex);
printf("thread_func: unlock\n");
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_mutex_init(&mutex, NULL);
if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
perror("pthread_create error");
return -1;
}
sleep(3);
pthread_cancel(tid);
pthread_join(tid, NULL);
return 0;
}
```
在上面的例子中,线程在持有锁的情况下被取消,导致锁无法被释放,造成死锁。为了避免这种情况,我们可以在取消线程前先释放它所持有的锁。修改后的代码如下:
```c
#include
#include
#include
#include
pthread_mutex_t mutex;
void cleanup(void *arg)
{
pthread_mutex_unlock((pthread_mutex_t *)arg);
}
void *thread_func(void *arg)
{
pthread_mutex_lock(&mutex);
pthread_cleanup_push(cleanup, &mutex);
printf("thread_func: lock\n");
sleep(10); // 此处加了等待时间以便观察
pthread_cleanup_pop(1);
printf("thread_func: unlock\n");
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_mutex_init(&mutex, NULL);
if (pthread_create(&tid, NULL, thread_func, NULL) != 0) {
perror("pthread_create error");
return -1;
}
sleep(3);
pthread_cancel(tid);
pthread_join(tid, NULL);
return 0;
}
```
在上面的代码中,我们使用了pthread_cleanup_push和pthread_cleanup_pop来保证取消线程时先释放锁。这样就避免了锁被永久占用的问题。
三、总结
pthread_cancel可以用于取消一个正在运行的线程。然而,要注意线程的取消状态和安全的状态下取消线程。避免导致线程取消问题,尤其是在持有锁的情况下,应该先释放锁再取消线程。使用pthread_cleanup_push和pthread_cleanup_pop可以保证在线程退出之前释放资源。多线程编程需要注意许多细节,在使用pthread_cancel时要特别小心,以避免导致程序崩溃或死锁等问题。