是计算机中独立运行的最小单位,运行时占用很少的系统资源。可以把线程看成是操作系统分配CPU时间的基本单元。一个进程可以拥有一个至多个线程。线程在进程内部共享地址空间、打开的文件描述符等资源。同时线程也有其私有的数据信息,包括:线程号、寄存器(程序计数器和堆栈指针)、堆栈、信号掩码、优先级、线程私有存储空间。
那可能有的人就会问了,既然我们已经有了进程,为什么还需要线程呢,线程比进程有哪些不可替代的优点呢?
1.和进程相比,它是一种非常”节俭”的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种”昂贵”的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
2.线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
3.除此之外和进程比较,多线程程序作为一种多任务、并发的工作方式,还有以下的优点: 1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。 2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于改修改。
进程和线程之间的共享资源和环境 共享: 1.文件描述符 2.每种信号的处理方式(SIG_IGN, SIG_DFL或者自定义信号处理函数) 3.当前工作目录 4.用户id和组id 但有些资源是每个线程所独有的: 1.线程id 2.上下文包括各种寄存器的值,程序计数器和栈指针 3.栈空间 4.errno变量 5.信号屏蔽字 6.调度优先级
返回值:成功返回0,失败返回错误号。
参数:
thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。
attr: 用于指定线程的属性
start_routine: 该参数是一个函数指针,指向线程创建后要调用的函数。
arg: 传递给线程函数的参数。
接下来我们来用代码简单创建一个进程。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h> void* thread_run(void *_val) { printf("%s pid is %d, tid is %d\n", (char*)_val, (int)getpid(), (unsigned long long)pthread_self()); return; } int main() { pthread_t tid; if(pthread_create(&tid, NULL, thread_run, "other thread run:") != 0) { printf("create thread error !\n"); return 0; } printf("main thread_run: pid is: %d, tid is: %d\n", (int)getpid(), (unsigned long long)pthread_self()); sleep(1); return 0; }这里有一个要提醒的问题,我们利用makefile实现线程相关的代码时可能linux会提醒这样一个错误
undefined reference to ‘pthread_create’ undefined reference to ‘pthread_joi’
问题原因: pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a,所以在使用pthread_create()创建线程,以及调用 pthread_atfork()函数建立fork处理程序时,需要链接该库。
问题解决: 在编译中要加 -lpthread参数 gcc thread.c -o thread -lpthread
这样就可以正确的创建出线程了 可以看到进程id是相同的,而线程id是不同的。
如果需要只终止某个线程而不终止整个进程,可以有三种方法: 1.从线程函数return。这种方法对主线程不适用,从main函数return相当于exit。
2.一个线程可以调用pthread_exit来终止自己。
3.线程还可以调用pthread_cancel终止同一进程中的另一个线程。
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_c reate函数之前调用。
线程只能被一个线程等待终止(第一个能正常返回),并且应处于join状态(非DETACHED)。
在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。
调用该函数的线程将挂起,等待 th 所表示的线程的结束。thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表示的线程必须是 joinable 的,即处于非 detached(游离)状态;并且只可以有唯一的一个线程对 th 调用 pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调用将返回错误。
如果你压根儿不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而来让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为 detached 状态可以通过两种方式来实现。后边我们详细说。
joinable 和 detached
可结合(joinable 状态 ):一个可结合的线程能够被其他进程回收资源和杀死,再被回收之前,他的存储器资源(例如栈)是释放的。 可分离(detached):一个分离的线程是不能被其他进程回收或杀死的,他的存储资源在它终止时由系统自动释放。
默认情况下,线程都是被创建成可结合的,为了避免存储器泄露,每个可结合的线程都应该要被显示回收或者调用pthread_join;或者调用pthread_detach函数分离。假如调用pthread_join运行后,该线程没用运行结束,调用者会被阻塞。显然主线程并不想看到这样,所以我们可以在子线程中加入代码
pthread_detach(pthread_self()) 或者父进程调用 pthread_detach(thread_id)(非阻塞,可立即返回)这样就将子线程状态设置成分离的(detached),那么该线程在运行结束时会自动释放所有资源。 下来我们用代码验证以上的结论:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 5 void* thread_run(void* _val) 6 { 7 pthread_detach(pthread_self()); 8 printf("%s\n", (char*)_val); 9 return NULL; 10 } 11 int main() 12 { 13 pthread_t tid; 14 if(pthread_create(&tid, NULL, thread_run, "other thread_run...") != 0) 15 { 16 printf("create thread error!\n"); 17 return ; 18 } 19 20 //wait 21 int ret = 0; 22 sleep(1); 23 if(0 == pthread_join(tid, NULL)) 24 { 25 printf("pthread wait success!\n"); 26 ret = 0; 27 } 28 else 29 { 30 printf("pthread wait failed!\n"); ret = 1; 32 } 33 return ret; 34 }程序员行结果:
从结果中我们可以看到在模拟分离之后,依旧在等待。 注意:在被分离的线程如果发生了严重的情况(例如除0 或者出错),依旧可以影响主线程。 后面我们将继续学习关于多线程的的其他知识,谢谢大家。
