线程
1 线程的基本概念
什么是线程
进程其实是一个容器,当我们在编程的时候实际上是在以线程为单位进行编程,包括处理器的调度也是以线程为单位的,一个进程可以有多个线程,一个进程的多个线程共享相同的进程空间,所以多线程之间的并发要比多进程之间的并发更加容易。除了共享进程空间的资源外,每个线程还有自己独有的数据:
- 进程中标识线程的线程标识符
- 寄存器信息
- 栈
- 信号屏蔽字
- 调度优先级和策略
- errno变量
- 其他私有数据
线程标准
线程是先有标准再有实现,所谓的pthread就是posix标准的实现
线程标识符
posix下的线程标识符就是pthread_t,这个数据可以是一个整数,也可以是一个结构体,所以不能单纯的将其当做一个整数来使用
- pthread_equal(3) 比较两个线程id,相同返回非0值,否则返回0值
int pthread_equal(pthread_t t1, pthread_t t2);
- pthread_self() 获取当前线程的线程id
pthread_t pthread_self(void);
2 线程的创建和终止
2.1 线程的创建
- pthread_create() 创建一个线程,成功返回0,失败返回errno
/* 1 thread是线程标识符
2 attr是线程的属性信息,由用户指定
3 start_routine是函数指针
4 arg是传递的参数
*/
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
- 示例
static void * func(void *p) {
puts("Thread is working...");
return NULL;
}
int main(int argc, char **argv) {
pthread_t tid;
int err;
puts("Begin!");
err = pthread_create(&tid, NULL, func, NULL);
if (err) {
fprintf(stderr, "pthread_create():%s", strerror(err));
exit(1);
}
puts("End!");
exit(0);
}
运行结果:可以看到有时候会输出线程中的语句,有时候不会输出,因为线程的调度策略是取决于CPU的,没输出的时候是因为main线程已经执行exit(0)推出了,这时进程也会退出所以不会执行线程的语句了,而输出就是因为在main线程执行exit(0)之前线程被调度了
root@VM-24-2-ubuntu:/home/ubuntu/linux# ./a.out
Begin!
End!
root@VM-24-2-ubuntu:/home/ubuntu/linux# ./a.out
Begin!
End!
root@VM-24-2-ubuntu:/home/ubuntu/linux# ./a.out
Begin!
End!
Thread is working...
2.2 线程的终止
终止方式
- 线程从启动例程返回,返回值就是线程的退出码
- 线程可以被同一进程中的其他线程取消
- 线程调用pthread_exit()函数
pthread_exit()函数
- 定义
void pthread_exit(void *retval)
pthread_join() 线程收尸,相当于wait操作
- 定义 等待子线程运行完
int pthread_join(pthread_t thread, void **retval);
2.3 栈清理
相关函数
注意:这两个其实是宏,需要成对出现,不然会出现语法错误
- pthread_cleanup_push() 类似装载钩子函数
void pthread_cleanup_push(void (*routine)(void *),
void *arg);
- pthread_cleanup_pop() 类似钩子函数被调用,不过进程中在其正常退出时会自己调用
void pthread_cleanup_pop(int execute);//传入0时表示只弹栈,但是不调用
示例
static void cleanup_func(void *p) {
puts(p);
}
static void *func(void *p) {
puts("Thread is working...");
pthread_cleanup_push(cleanup_func, "cleanup:1") ;
pthread_cleanup_push(cleanup_func, "cleanup:2") ;
pthread_cleanup_push(cleanup_func, "cleanup:3") ;
puts("push over");
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_cleanup_pop(1);
pthread_exit(NULL);
}
int main(int argc, char **argv) {
pthread_t tid;
int err;
puts("Begin!");
err = pthread_create(&tid, NULL, func, NULL);
if (err) {
fprintf(stderr, "pthread_create():%s", strerror(err));
exit(1);
}
pthread_join(tid, NULL);
puts("End!");
exit(0);
}
输出结果:
Begin!
Thread is working...
push over
cleanup:3
cleanup:2
cleanup:1
End!
2.4 线程取消
相关函数
- pthread_cancel() 给thread发送取消请求,成功返回0否则返回error number
int pthread_cancel(pthread_t thread);
- 取消的状态:允许和不允许
- 允许取消又分为异步cancel和推迟cancel(推迟至cancel点再响应)
- cancel点:POSIX定义的cancel点都是可能引发阻塞的系统调用
- pthread_setcancelstate() 设置取消状态,即是否允许被取消
int pthread_setcancelstate(int state, int *oldstate);
- pthread_setcanceltype() 设置取消方式:异步或推迟
int pthread_setcanceltype(int type, int *oldtype);
- pthread_testcancel() 这个函数不做任何事,就是一个取消点
void pthread_testcancel(void);
2.5 线程分离
-
pthread_detach() 分离一个线程
线程分离后即再也不能对该线程做其他操作,任由其自生自灭
int pthread_detach(pthread_t thread);
2.7 进程和线程原语的比较
2.8 线程竞争实例
判断质数
#define LEFT 30000000
#define RIGHT 30000200
static void *func(void *p) {
int j;
int num = *(int *)p;
for (j = 2; j < num / 2; j++) {
if (num % j == 0) {
return NULL;
}
}
printf("%d is a primer\n", num);
pthread_exit(NULL);
}
int main(int argc, char **argv) {
pthread_t tids[RIGHT - LEFT + 1];
int err;
int i, j;
for (i = LEFT; i < RIGHT; i++) {
err = pthread_create(&tids[i - LEFT], NULL, func, &i);
if (err) {
fprintf(stderr, "pthread_create():%s", strerror(err));
for (j = LEFT; j < i; j++) {
pthread_join(&tids[j - LEFT], NULL);
}
exit(1);
}
}
for (i = LEFT; i < RIGHT; i++) {
pthread_join(tids[i - LEFT], NULL);
}
exit(0);
}
输出结果:可以看到结果是有问题的,即产生了线程竞争的情况,因为在创建线程的时候i采用了地址传参,可能在线程还没有执行到num那条语句的时候,main线程又执行了i++,从而导致前面创建的线程的i发生了变化
30000109 is a primer
30000001 is a primer
30000109 is a primer
既然是因为地址传参,使得每次传参的地址一样导致的竞争,那么我们可以用一个结构体来保存我们的参数,每次传入不同的结构体指针,这样就能避免线程竞争同一块地址了
struct targ {
int n;
};
static void *func(void *p) {
int j, mark = 1;
int num = ((struct targ *)p)->n;
for (j = 2; j < num / 2; j++) {
if (num % j == 0) {
mark = 0;
break;
}
}
if (mark) {
printf("%d is a primer\n", num);
}
pthread_exit(p);
}
int main(int argc, char **argv) {
pthread_t tids[RIGHT - LEFT + 1];
int err;
int i, j;
struct targ *num;
void *p;
for (i = LEFT; i < RIGHT; i++) {
num = malloc(sizeof (*num));
if (num == NULL) {
perror("malloc():");
for (j = LEFT; j < i; j++) {
pthread_join(tids[j - LEFT], NULL);
}
exit(1);
}
num->n = i;
err = pthread_create(&tids[i - LEFT], NULL, func, num);
if (err) {
fprintf(stderr, "pthread_create():%s", strerror(err));
for (j = LEFT; j < i; j++) {
pthread_join(tids[j - LEFT], NULL);
}
exit(1);
}
}
for (i = LEFT; i < RIGHT; i++) {
pthread_join