线程是一种使程序在同一时间做多件事的机制,和进程一样是并发执行的。linux内核调度为每个线程分配一个时间片,使用完后等待下次调度。和进程相比,线程是一种更小的执行单位。
每个进程启动后都会有一个线程在运行,称为主线程,可以在主线程中启动多个子线程,这些线程在同一个进程中,不同线程在给定时间内执行不同的代码片段。
我们可以fork一个子进程,这个子进程就是对父进程的一个copy,包括系统分配的各种资源:虚拟内存、文件描述符等。如果在子进程关闭文件描述符,不会影响父进程对其的读写。但线程不同,每个线程都共享同一内存、文件描述符、以及其他资源。在一个线程中关闭文件描述符,其他的线程都不能读写。
在任何一个线程中调用exec,所有线程都会停止,并且当前线程所在的进程会被exec传递的程序替换。
1.线程创建
所有与线程相关的函数和数据类型都在<pthread.h>中声明,但线程函数没有被包含进C标准库中,因此,调用了线程相关API后,编译时需要添加线程库:libpthread,或者-lpthread
每个线程用一个唯一的ID表示,称为线程id,编程中类型为pthread_t.
创建一个线程很简单,只需调用以下函数即可,创建之后线程会等待系统调用随时会启动。
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
参数介绍:
thread :线程的ID,一个pthread_t类型的指针
attr: 线程的属性,一般赋值为NULL,表示使用默认值
start_routine :线程函数,在线程创建后需要执行的代码。一个普通函数,返回值、参数类型都是void*。这样可以传递任何类型的数据,只需将数据结构的指针传递,然后接收 到时强制转换。
arg :线程函数start_routine的参数
创建两个线程,分别打印“hello”和“world”,主线程打印"main"
#include <pthread.h> #include <stdio.h> void* thread_func(void* s) { int n=10; while(n-- >0) printf("%s \n",(char*)s); return NULL; } int main() { int g=10; pthread_t thread_id_hl; pthread_t thread_id_wd; pthread_create(&thread_id_hl,NULL,thread_func,"hello"); pthread_create(&thread_id_wd,NULL,thread_func,"world"); while(g-- >0) printf("main\n"); return 0; }
以上代码中创建两个线程,线程函数相同只是传递的参数不同,运行之后可以看到打印"main"个数每次都是10个,“hello”,"world"个数不够10个。只是因为main线程在两个子线程没有退出之前结束。
要等待子线程都退出之后main线程在退出,和进程中的wait相似,线程中使用pthread_join();
int pthread_join(pthread_t thread,void **retval);
参数介绍:
thread: 等待退出线程的id
retval: 线程退出时的返回值,通过指针的方式传递个*retval,如果调用pthread_join时指定的线程已经退出,*retval 会返回PTHREAD_CANCLED
一个线程的退出可以通过两种方式:1.等待线程函数执行到return返回 ;2.在线程中执行void pthread_exit(void *retval),retval为线程返回的值。无论用哪种返回方式,返回值都会在pthread_join的参数retval中获取。
如下程序,创建两个线程调用同一个函数,对线程函数传递的参数不同其返回值也不同:#include <pthread.h>
#include <stdlib.h> #include <string.h> #define THREAD_ONE 1 #define THREAD_TWO 2 void* thread_function(void *arg) { char *retval; int n = *((int*)arg); switch(n) { case THREAD_ONE: retval="get from thread one.\n"; return retval; case THREAD_TWO: retval="get from thread two.\n"; pthread_exit((void*)retval); } return NULL; } int main() { int thread_arg1 = THREAD_ONE; int thread_arg2 = THREAD_TWO; pthread_t thread_one; pthread_t thread_two; char *retval,*mallocaddr; pthread_create(&thread_one,NULL,&thread_function,(void*)&thread_arg1); pthread_create(&thread_two,NULL,&thread_function,(void*)&thread_arg2); mallocaddr = (char*)malloc(64); memset(mallocaddr,0,64); retval = mallocaddr; pthread_join(thread_one,(void**)&retval); printf("The return value. %s",retval); pthread_join(thread_two,(void**)&retval); printf("The return value. %s",retval); free(mallocaddr); return 0; }
如以上示例,通常一个函数可能被多个线程调用,有时候函数需要知道自己被哪个进程正在运行,可以在函数中调用:pthread_t pthread_self(void)返回正在运行函数的线程id
如果在一个线程中调用pthread_join并将自己的id作为参数,函数会立刻出现错误,并返回EDEADLK。为了避免一个线程pthread_join自己的id,可以这样:
if (!pthread_equal (pthread_self (), other_thread)) pthread_join (other_thread, NULL);
pthread_equal 函数定义:
int pthread_equal(pthread_t t1, pthread_t t2);
2.线程的joinable和detach状态
joinable状态的线程,当线程结束之后不会自动释放资源,直到其他线程调用pthread_join该线程的返回值时才会释放。
detach 状态的线程,线程结束之后会自动释放资源,其他线程不能调用pthread_join获取他的返回值。
线程这两种状态由创建时pthread_create传递的属性attr参数来控制,默认情况下为joinable状态。
将线程设置为detach状态:
pthread_attr_t attr; pthread_t thread; pthread_attr_init (&attr); pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED); pthread_create (&thread, &attr, &thread_function, NULL); pthread_attr_destroy (&attr);
在这里,多个线程创建可以使用同一个attr,使用完后要用pthread_attr_destroy删除,下次用时在phtread_attr_init
注:一个线程创建时是joinable状态,可以用pthread_detach将其转换成detach状态,反之,不可以。