对于一个进程来说,即使没有主动去创建线程,进程也有默认的主线程,线程是项目的实际执行者,根据项目执行计划书,一行行的执行下去
进程好比是一个项目,线程就是将项目需求,拆分为一个个开发任务,将任务拆分出来,拆分的足够细致,就可以加快开发的进度
这样分别更加适合开发
如果将任务分为两个进程去执行,那么会出现因为会议室是独立,而事情不受控制的情况
而且创建进程占用的资源太多,
最终因为进程之间无法传递数据导致无法共享的问题
在一个进程中,一般也不会只有一个主线程,毕竟希望可以管控风险,可以在一些不是很大的中断出现的时候,交给单独的线程去处理这件事
那么如何去创建线程
假如我们有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); pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_JOINABLE); for(t=0;t<NUM_OF_TASKS;t++){ printf(“creating thread %d, please help me to download %s\n”, t, files[t]); 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(threads[t],(void**)&downloadtime); printf(“Thread %d downloads the file %s in %d minutes.\n”,t,files[t],downloadtime); } pthread_exit(NULL); } |
函数最后调用了pthread_exit函数进行退出线程
函数传入了一个参数为void *类型为线程退出时候的返回值
我们声明一个属性 pthread_attr_t,利用这个属性来进行初始化,并设置PTHREAD_CREATE_JOINABLE,表示等待这个线程结束,获取退出时候的状态
然后是一个循环,对于每个文件和线程,我们创建一个线程,利用函数 pthread_create创建线程,一共有四个参数,第一个是线程对象,然后是线程属性,然后线程运行函数,第四个参数是线程运行函数的参数
任务分配完成,然后等待每个线程下载对应的文件,然后等待这个子任务完成之后,发送信号给其他所有的进程,主线程利用pthread_join获取到下载的消耗时间,告诉主线程
程序完成了,开始编译,多线程程序要依赖于libpthread.so
gcc download.c -lpthread
那么我们就可以进行运行,得到一个普通线程的创建和运行
多线程情况下的数据,基本分为了三类
线程本地数据
函数执行过程中的局部变量,因为是在本地栈上的,所以基本没问题
栈的大小可以利用命令 ulimit -a查看,默认为8MB,可以使用ulimit -s修改
对于线程栈可以修改对应的大小,利用如下的函数
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
进程内共享的全局数据,因为是全局共享的,所以需要一种机制保护其,就是锁机制
最后就是线程私有数据,利用函数创建
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))
这个key被创建了之后,所有的线程都可以进行访问,各个线程都可以key中放入值,提供一个同名不同值的全局变量
int pthread_setspecific(pthread_key_t key, const void *value)
然后是根据key获取到对应的value
void *pthread_getspecific(pthread_key_t key)
我们来说一下数据的保护问题
在Linux中,有对应的互斥工具,Mutex,锁机制
#include <pthread.h>
#include <stdio.h> #include <stdlib.h> #define NUM_OF_TASKS 5 int money_of_tom = 100; int money_of_jerry = 100; //第一次运行去掉下面这行 pthread_mutex_t g_money_lock; void *transfer(void *notused) { pthread_t tid = pthread_self(); printf(“Thread %u is transfering money!\n”, (unsigned int)tid); //第一次运行去掉下面这行 pthread_mutex_lock(&g_money_lock); sleep(rand()%10); money_of_tom+=10; sleep(rand()%10); money_of_jerry-=10; //第一次运行去掉下面这行 pthread_mutex_unlock(&g_money_lock); printf(“Thread %u finish transfering money!\n”, (unsigned int)tid); pthread_exit((void *)0); } int main(int argc, char *argv[]) { pthread_t threads[NUM_OF_TASKS]; int rc; int t; //第一次运行去掉下面这行 pthread_mutex_init(&g_money_lock, NULL); for(t=0;t<NUM_OF_TASKS;t++){ rc = pthread_create(&threads[t], NULL, transfer, NULL); if (rc){ printf(“ERROR; return code from pthread_create() is %d\n”, rc); exit(-1); } } for(t=0;t<100;t++){ //第一次运行去掉下面这行 pthread_mutex_lock(&g_money_lock); printf(“money_of_tom + money_of_jerry = %d\n”, money_of_tom + money_of_jerry); //第一次运行去掉下面这行 pthread_mutex_unlock(&g_money_lock); } //第一次运行去掉下面这行 pthread_mutex_destroy(&g_money_lock); pthread_exit(NULL); } |
如果没有Mutex相关的行,那么就会出现数据不对的问题
如果有这些Mutex的行,数据必然是正确的
使用mutex,首先要使用 pthread_mutex_init函数初始化这个mutex,初始化之后,就可以保护共享变量了
对应的加锁解锁的函数如下
phtread_mutex_lock(),尝试去加锁,加上了,就继续执行,不然就阻塞
pthread_mutex_trylock(),尝试加锁,加不上,直接返回错误码
释放锁
pthread_mutex_unlock进行释放
最后一个主线程利用pthread_mutex_destory销毁嗦
其次就是根据锁带来的条件变量的问题,就是对应的condition,利用条件变量,可以进行对应线程的唤醒,告诉他,可以获取锁了,但是可以获取锁,不等于一定可以获取到锁
#include <pthread.h>
#include <stdio.h> #include <stdlib.h> #define NUM_OF_TASKS 3 #define MAX_TASK_QUEUE 11 char tasklist[MAX_TASK_QUEUE]=”ABCDEFGHIJ”; int head = 0; int tail = 0; int quit = 0; pthread_mutex_t g_task_lock; pthread_cond_t g_task_cv; void *coder(void *notused) { pthread_t tid = pthread_self(); while(!quit){ pthread_mutex_lock(&g_task_lock); while(tail == head){ if(quit){ pthread_mutex_unlock(&g_task_lock); pthread_exit((void *)0); } printf(“No task now! Thread %u is waiting!\n”, (unsigned int)tid); pthread_cond_wait(&g_task_cv, &g_task_lock); printf(“Have task now! Thread %u is grabing the task !\n”, (unsigned int)tid); } char task = tasklist[head++]; pthread_mutex_unlock(&g_task_lock); printf(“Thread %u has a task %c now!\n”, (unsigned int)tid, task); sleep(5); printf(“Thread %u finish the task %c!\n”, (unsigned int)tid, task); } pthread_exit((void *)0); } int main(int argc, char *argv[]) { pthread_t threads[NUM_OF_TASKS]; int rc; int t; pthread_mutex_init(&g_task_lock, NULL); pthread_cond_init(&g_task_cv, NULL); for(t=0;t<NUM_OF_TASKS;t++){ rc = pthread_create(&threads[t], NULL, coder, NULL); if (rc){ printf(“ERROR; return code from pthread_create() is %d\n”, rc); exit(-1); } } sleep(5); for(t=1;t<=4;t++){ pthread_mutex_lock(&g_task_lock); tail+=t; printf(“I am Boss, I assigned %d tasks, I notify all coders!\n”, t); pthread_cond_broadcast(&g_task_cv); pthread_mutex_unlock(&g_task_lock); sleep(20); } pthread_mutex_lock(&g_task_lock); quit = 1; pthread_cond_broadcast(&g_task_cv); pthread_mutex_unlock(&g_task_lock); pthread_mutex_destroy(&g_task_lock); pthread_cond_destroy(&g_task_cv); pthread_exit(NULL); } |
看如上的代码
创建了10个任务,每个任务一个字符,另外有两个变量 head/tail
表示工作从哪里开始,到哪里结束
如果head 等于 tail,则说明工作分配完毕,如果tail加N,就是重新分配了N个工作
那么对于每一个员工,就要获取到锁,查看对应的head是否可以获取任务,有了任务就可以拿到任务,进行解锁并工作,
如果head和tail相等,则需要等待,调用 pthread_cond_wait进行等待,这个函数会将锁作为变量传进去,等待的过程中需要解锁,避免阻塞他人
boss也就是主线程,初始化了条件变量和锁,然后创建了是哪个线程
然后分配任务,一共10个,分四批分配,
分配的时候,先获取到锁 pthread_mutex_lock,然后tail加1即可,还需要利用pthread_cond_broadcast进行通知员工
然后进行抢锁,抢到了需要判断head tail是否相同,有了任务,释放锁,然后继续抢,直到任务结束,进入wait状态
整体套路就是这样
我们说了一下如何创建线程,以及线程的数据和线程数据的保护
整体总结如下