线程学习中的错误总结
线程是计算机中非常重要的概念,线程是计算机中独立运行的最小单位,运行时占用很少的系统资源。每个线程占用CPU的时间由系统分配,所以可以也可以把线程看成是操作系统分配CPU时间的基本单位。在单核CPU电脑上各个进程交替执行,系统不停地切换就仿佛是在同时执行一样。
线程和进程相比较优点有很多,其主要可归结为创建速度快、切换快、线程间通信方便、占用系统资源少等。虽然线程在进程的内部共享地址空间、打开的文件描述符等资源但是它也有其私有的属性信息,主要包括:线程号、寄存器、堆栈、信号掩码、、优先级、线程私有数据等。。。现在将线程学习这一周来出现的错误总结如下,以此来警示自己。
一、创建线程和线程结束中出现的错误
1)、线程创建后忘记等待致使进程已经结束但线程还未执行
创建一个线程使用int pthread_create(pthread_t *thread, pthread_attr_t *attr, void*(*start_routine),void *arg);函数。函数中pthread_t和pthread_attr_t这两个类型在linux下的声明如下图所示,该声明在/usr/include/bits/pthreadtypes.h中(ubuntu等系统在/usr/include/x86_64-linux-gnu/bits/pthreadtypes.h中)。
函数中thread表示一个pthread_t类型变量的指针,创建成功后新线程的ID号会存在thread所指向的变量中。attr参数表示要创建的线程属性(此处为NULL表示默认)。start_routine参数表示一个函数指针,指向创建完线程后要调用的函数(该函数被成为线程函数)。arg表示要传进线程函数的参数。函数创建成功则返回0,失败返回非0。线程创建就是一个函数调用,但是在第一次写一个创建线程的练习时却出现了以下情况:
<span style="font-size:18px;">#include <stdio.h> #include <stdlib.h> #include <pthread.h> void func(void) { pthread_t newthid = pthread_self(); printf("新线程中,线程ID是:%lu\n",newthid); } int main(void) { pthread_t thid; int err; printf("主线程中,线程ID是:%lu\n",pthread_self()); if ((err = pthread_create(&thid,NULL,(void *)func,NULL)) != 0){ printf("CreatError:%d",err); exit(-1); } return 0; }</span>本段代码看似没有问题,执行结果却没有出现我们想要的结果:
从结果中可以看出程序并没有执行新创建的线程,出现该问题的原因是主线程创建了一个线程,但是子线程还没有轮到它占用CPU执行的时候创建它的进程已经结束了,这时候线程也就没有机会执行了。可以在创建完线程之后让创建它的进程sleep一下就可以让新线程切换过去有机会执行,或者在进程的结尾处使用pthread_join()函数等待线程执行结束后在结束进程。
2)、pthread_join()函数的误用
函数 int pthread_join(pthread_t th,void **thread_return)功能是等待一个线程结束并回收线程的资源。参数th表示要等待的线程ID,函数执行成功后会将线程退出的错误码存在thread_return指向的变量中。
<span style="font-size:18px;">#include <stdio.h> #include <pthread.h> #include <unistd.h> void func(void) { printf("Helloworld!\n"); pthread_exit(0); } int main(void) { int a = 10,status; pthread_t pth; printf("a = %d\n",a); if (pthread_create(&pth,NULL,(void *)func,NULL) != 0) printf("创建失败!\n"); pthread_join(pth,(void *)&status); printf("status = %d\n",status); printf("a = %d\n",a); return 0; }</span>该程序预期执行结果是:
a = 10
Helloworld!
status = 0
a = 10
但是。。。。实际结果是这样的
出现这样的结果是因为pthread_join()函数的误用,因为pthread_join()函数的第二个参数应该是个二级指针,函数会将返回的退出码存成二级指针所知向的一级指针的值(不是内容,这一点类似于下一个总结的函数)而程序中定义了一个int型的变量,它的值只能存4个字节的内容,但是64位机的指针占8个字节,他会将和它相连的四个字节覆盖掉,所以a的内容变成了0,如果将其改变为double型的话就ok了,但是改成double不符合函数的参数类型,严格来说是用错了,所以还是定义成int
*status更规范,也不容易出错。
二、私有数据和线程同步
1)、私有数据中的pthread_setspecific(pthread_key_t key,const void *pointer)中的key关联的是pointer的值而不是pointer所指向的内存中的值。例如:
<span style="font-size:18px;">int a = 5; pthread_setspecific(key,(void *)a);</span>这段代码中就是将a这个int型的值强制类型转换成(void *)型与key关联的是a转换成(void *)的值,也就是5,而不是内存编号为5的这块内存中的数据。
2)、线程同步中的互斥锁锁住的是线程而不是数据。
互斥锁加锁函数pthread_mutex_lock(pthread_mutex *mutex);和解锁函数pthread_mutex_unlock(pthread_mutex_t *mutex);中间部分是所要保护的数据,系统是通过挂起线程来达到间接保护加锁数据的目的,而不是直接对数据进行保护。
<span style="font-size:18px;">#include <stdio.h> #include <pthread.h> pthread_mutex_t number_mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t number_mutex1 = PTHREAD_MUTEX_INITIALIZER; int a = 0; int b = 10; void write_globalnumber(void) { pthread_mutex_lock(&number_mutex); a++; sleep(5); pthread_mutex_unlock(&number_mutex); } void read_globalnumber(void) { printf("b = %d\n",b); pthread_mutex_lock(&number_mutex); printf("哈哈\n"); printf("a = %d\n",a); pthread_mutex_unlock(&number_mutex); } int main(void) { pthread_t thid1,thid2; if (pthread_create(&thid1,NULL,(void *)write_globalnumber,NULL) != 0) printf("线程1失败\n"); sleep(1); if (pthread_create(&thid2,NULL,(void *)read_globalnumber,NULL) != 0) printf("线程2失败\n"); pthread_join(thid1,NULL); pthread_join(thid2,NULL); return 0; }</span>
此程序的执行结果:
b = 10
哈哈
a = 1
当前程序的打印结果的时间上的差异为:先停顿一秒打印出b = 10后停顿再停顿四秒打印后边的哈哈和a =1。
如果将func2中的number_mutex改为number_mutex1则先停顿一秒后打印出b = 10再接着打印哈哈和a = 1。
从第一种结果可以看出当锁变量相同时第二个线程在碰到该锁时打印出的 哈哈 是和要保护的变量a一起打印出来的,说明了线程碰到锁是将线程挂起从而保护数据,而不是直接给数据加了保护。
从第二中结果也看出加锁是通过变量number_mutex实现,如果该变量不同则会保护失败,由此也可以看出是通过number_mutex的值来间接保护加锁数据的而不是直接对数据进行保护。