对于任何一个进程来讲,即便我们没有主动去创建线程,进程也是默认有一个主线程的。线程是负责执行二进制指令的,它会根据项目执行计划书,一行一行执行下去。进程要比线程管的宽多了,除了执行指令之外,内存、文件系统等等都要它来管。
所以,进程相当于一个项目,而线程就是为了完成项目需求,而建立的一个个开发任务。默认情 况下,你可以建一个大的任务,就是完成某某功能,然后交给一个人让它从头做到尾,这就是主 线程。但是有时候,你发现任务是可以拆解的,如果相关性没有非常大前后关联关系,就可以并 行执行。
进程之间的并行存在的问题:创建进程占用的资源太多。进程之间不存在共享的数据空间,进程之间的通信需要数据在不同的内存中传来传去。
创建线程
以下载N个大视频为例子,一个一个下载时间太长了,可以拆分成N个任务,分给N个线程各自去下载。将线程需要执行的子任务放在一个参数类型是void类型的指针,用于接收任何类型的参数。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_OF_TASKS 5//数组大小
void *downloadfile(void *filename)
{
printf("I am downloading the file %s!\n", (char *)filename);
sleep(10);
long downloadtime = rand()%100;
printf("I finish downloading the file within %d minutes!\n", downloadtime);
pthread_exit((void *)downloadtime);//退出线程的函数,参数是线程退出的返回值
}
int main(int argc, char *argv[])
{
char files[NUM_OF_TASKS][20]={"file1.avi","file2.rmvb","file3.mp4","file4.wmv","file5.flv"};
pthread_t threads[NUM_OF_TASKS];
int rc;
int t;
int downloadtime;
pthread_attr_t thread_attr;
pthread_attr_init(&thread_attr);//初始化thread_attr
//这表示将来主线程程等待这个线程的结束,并获取退出时的状态
pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_JOINABL);
for(t=0;t<NUM_OF_TASKS;t++){
printf("creating thread %d, please help me to download %s\n", t, files[t]);
//对于每一个文件和每一个线程,可以调用pthread_create创建线程
//第一个参数是线程对象,第二个参数是线程的属性,第三个参数是线程运行函数,第
//四个参数是线程运行函数的参数
rc = pthread_create(&threads[t], &thread_attr, downloadfile, (void *)files[t]);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_attr_destroy(&thread_attr);
for(t=0;t<NUM_OF_TASKS;t++){
//使用pthread_join获取这个线程退出的返回值。线程的返回值通过pthread_join传给主线程
pthread_join(threads[t],(void**)&downloadtime);
printf("Thread %d downloads the file %s in %d minutes.\n",t,files[t],downloadtime);
}
pthread_exit(NULL);
}
线程创建和运行的过程
线程的数据
线程可以将任务并行起来,那么线程的数据如何合并起来呢?可以将线程访问的数据分成三类:
第一类是线程栈上的本地数据,比如函数执行过程中的局部变量。函数的调用会使用栈的模型,这在线程里面是一样的。只不过每个线程都有自己的栈空间。为了避免线程之间的栈空间踩踏,线程栈之间还会有小块区域,用来隔离保护各自的栈空间。一旦另一个线程踏入到这个隔离区,就会引发段错误。
第二类数据就是在整个进程里共享的全局数据。例如全局变量,虽然在不同进程中是隔离的,但 是在一个进程中是共享的。
这就是第三类数据,线程私有数据(Thread Specific Data),可以通过以下函数创建:
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))
key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值, 这就相当于提供了一个同名而不同值的全局变量。
数据的保护
如果同一个全局变量,两个线程一起修改,那肯定会有问题,有可能把数据改的面目全非。这就需要有一种机制来保护他们。
第一种方式:Mutex
中文叫互斥。它的模式就是在共享数据访问的时候,去申请加把锁,谁先拿到锁,谁就拿到了访问权限,其他人就只好在门外等着,等这个人访问结束,把锁打开,其他人再去争夺,还是遵循谁先拿到谁访问。
使用Mutex,首先要使用pthread_mutex_init函数初始化这个mutex,初始化后,就可以用它来保护共享变量了。
pthread_mutex_lock() 就是去抢那把锁的函数,如果抢到了,就可以执行下一行程序,对共享变量进行访;如果没抢到,就被阻塞在那里等待。
如果不想被阻塞,可以使用pthread_mutex_trylock去抢那把锁,如果抢到了,就可以执行下一行程序,对共享变量进行访问;如果没抢到,不会被阻塞,而是返回一个错误码。
使用pthread_mutex_unlock释放锁,让给其他人使用,最终调用pthread_mutex_destroy销毁掉这把锁。