为什么要使用多线程?
传统的linux进程可以看成只有一个控制线程:同一进程在同一时刻只能执行一件事情。有了多个控制线程后,我们可以把程序设计成在同一时刻不只做一件事情。进程是系统中程序执行和资源分配的基本单位。每个进程有自己的数据段、代码段和堆栈段。这就造成进程在进行切换等操作时都需要有比较负责的上下文切换等动作。为了进一步减少处理器的空转时间支持多处理器和减少上下文切换开销,也就出现了线程。(就是防止进程某一部分等待,导致进程白白切换)。
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
一个线程死掉,整个进程死掉,多进程程序比多线程程序健壮
线程与进程相比:父子进程有独立的存储空间,数据不会共享,线程节省存储空间,数据共享
核心:pthread_create(pthread_t* thread, pthread_attr_t * attr, void *(*start_routine)(void *), void * arg);
(1)创建的线程名字
(2)常常NULL
(3)线程执行的函数
(4)给该函数传参
如果该函数参数不止一个,那就把该函数的参数做成一个结构体,把该结构体地址作为参数传进去。
pthread_join(pthread_t pthid, void **thread_return);
(1)等待的线程名字。
(2)thread_return 是一个传出参数,接收线程函数的返回值。类型(void **)
pthread_self():打印线程ID号
一、线程的创建及等待
#include <pthread.h>
#include <stdio.h>
void *func1(void* arg)
{
static char* p = "t1 is run over";//必须要用static
printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
pthread_exit((void*)p);//线程退出并且返回p的内容
}
int main()
{
pthread_t t1;
int param = 100;//给线程传参
int ret;//定义线程返回标志
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
if(ret == 0)
{
printf("main: pthred is creat success\n");
}
printf("main: %ld\n",(unsigned long)pthread_self());//获取主函数线程号
char* pret = NULL;//接收线程返回的标志,因为线程默认void无类型
pthread_join(t1,(void **)&pret);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退出。
printf("main is run over,%s\n",pret);//打印出pret内容
return 0;
}
二、多线程空间共享代码验证:
注:进行编译时一定要链接 -lpthread库
#include <pthread.h>
#include <stdio.h>
int g_data = 0;
void *func1(void* arg)
{
//static char* p = "t1 is run over";//必须要用static
printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
//pthread_exit((void*)p);
while(1)
{
printf("t1:%d\n",g_data++);
sleep(1);
}
}
void *func2(void* arg)
{
//static char* p = "t1 is run over";//必须要用static
printf("t2:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t2.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
//pthread_exit((void*)p);
while(1)
{
printf("t2:%d\n",g_data++);
sleep(1);
}
}
int main()
{
int ret1;//定义线程返回标志
int ret2;
int param = 100;//给线程传参
pthread_t t1;
pthread_t t2;
// char* pret = NULL;
ret1 = pthread_create(&t1,NULL,func1,(void *)¶m);
ret2 = pthread_create(&t2,NULL,func2,(void *)¶m);
if(ret1 == 0 && ret2 == 0)
{
printf("main: pthred is creat success\n");
}
printf("main: %ld\n",(unsigned long)pthread_self());//获取主函数线程号
while(1)
{
printf("main:%d\n",g_data++);
sleep(1);
}
pthread_join(t1,NULL);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退
出。
pthread_join(t2,NULL);
// printf("main is run over,%s\n",pret);
return 0;
}
三、互斥量与锁:
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。每次只有一个线程可以向前运行。
#include <pthread.h>
#include <stdio.h>
int g_data = 0;
pthread_mutex_t mutex;//创建互斥锁
void *func1(void* arg)
{
pthread_mutex_lock(&mutex);//给t1上锁
printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
pthread_mutex_unlock(&mutex);//给t1解锁
}
void *func2(void* arg)
{
pthread_mutex_lock(&mutex);
printf("t2:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t2.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
pthread_mutex_unlock(&mutex);
}
int main()
{
int ret;//定义线程返回标志
int param = 100;//给线程传参
pthread_t t1;
pthread_t t2;
char* pret = NULL;
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
if(ret == 0)
{
printf("main: t1 pthred is creat success\n");
}
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
if(ret == 0)
{
printf("main: t2 pthred is creat success\n");
}
printf("main: %ld\n",(unsigned long)pthread_self());//获取主函数线程号
pthread_join(t1,NULL);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退出。
pthread_join(t2,NULL);
return 0;
}
这里互斥锁只能保证执行t1或者t2运行期间,其他线程不会运行,不能保证一定是t1先运行。
#include <pthread.h>
#include <stdio.h>
void *func1(void* arg)
{
pthread_mutex_lock(&mutex);//确保一进t1就被锁死
printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
while(1)//假设就是t1先运行
{
printf("t1:%d\n",g_data++);
if(g_data == 3)
{
pthread_mutex_unlock(&mutex);
printf("==========t1 quit=========");
pthread_exit(NULL);
}
}
}
void *func2(void* arg)
{
printf("t2:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t2线程号
printf("t2.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
while(1)//假设t2先运行
{
printf("t2:%d\n",g_data);
pthread_mutex_lock(&mutex);
g_data++;
pthread_mutex_unlock(&mutex);
sleep(1);//在这段时间里t2睡眠,且没有锁,一定可以保证t1可以争夺到位置且立马上锁
}
}
int main()
{
int ret;//定义线程返回标志
int param = 100;//给线程传参
int g_data = 0;
pthread_mutex_t mutex;//创建互斥锁
pthread_t t1;
pthread_t t2;
char* pret = NULL;
pthread_mutex_init(&mutex,NULL);//初始化锁
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
if(ret == 0)
{
printf("main: t1 pthred is creat success\n");
}
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
if(ret == 0)
{
printf("main: t2 pthred is creat success\n");
}
printf("main: %ld\n",(unsigned long)pthread_self());//获取主函数线程号
pthread_join(t1,NULL);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退出。
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);//销毁锁
return 0;
}
死锁情况:两个进程本身都有一个自己的锁,在对方没解锁的情况下又想拿到对方的锁,就会造成死锁。
四、条件锁:
pthread_cond_signal(&cond);//条件发生
pthread_cond_wait(&cond,&mutex);//运行等待处以后的代码
#include <pthread.h>
#include <stdio.h>
int g_data = 0;
pthread_mutex_t mutex;//创建互斥锁
pthread_cond_t cond;//创建条件线程
void *func1(void* arg)
{
printf("t1:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t1线程号
printf("t1.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
while(1)
{
pthread_cond_wait(&cond,&mutex);//等待上锁条件,条件满足才运行,无条件则阻塞,那t2便获得运行权限
printf("========t1 run=======");
printf("%d\n",g_data);
g_data = 0;
sleep(1);
}
}
void *func2(void* arg)
{
printf("t2:%ld pthread is creat\n",(unsigned long)pthread_self());//获取t2线程号
printf("t2.arg = %d\n",*(int*)arg);//把arg转化为int*型,然后取内容
while(1)//假设t2先运行
{
printf("t2:%d\n",g_data);
pthread_mutex_lock(&mutex);
g_data++;
if(g_data == 3)
{
pthread_cond_signal(&cond);//(达到上锁条件)满足条件,转到func1里的wait
}
pthread_mutex_unlock(&mutex);
sleep(1);//在这段时间里t1可与t2竞争获得锁权限
}
}
int main()
{
int ret;//定义线程返回标志
int param = 100;//给线程传参
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);//初始化锁
pthread_cond_init(&cond,NULL);//初始化条件锁
ret = pthread_create(&t1,NULL,func1,(void *)¶m);
ret = pthread_create(&t2,NULL,func2,(void *)¶m);
pthread_join(t1,NULL);//pret是为了获取线程退出所返回的内容,无返回值则NULL,等待线程退出再往下执行,否则主进程直接退出,线程来不及运行就退出。
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);//销毁锁
return 0;
}
加入t1先运行,那么t1则阻塞,此时t2获取锁运行,当t2把参量运行到3时,t1达到运行条件开始运行,运行完参量归0,延时1秒,此时t2开始运行。
五、我们写一个脚本用来记录运行数据
int main(int argc,char **argv)
{
int time = atoi(argv[1]);
int i;
for(i=0;i<time;i++)
{
system("./demo");
}
return 0;
}
gcc test.c -o test
./test 3 >>test.ret.txt把运行结果追加到test.ret.txt文本里,这个文本会自动生成,这个demo需要能自行结束才能追加。