进程:一个正在执行的程序,它是资源分配的最小单位
进程中事情需要按照一定的顺序逐个进行,那么如何让一个进程中的一些事情同时执行?
线程:线程(thread)是包含在进程内部的顺序执行流,是进程中的实际运作单位,也是操作 系统能够进行调度的最小单位。一个进程中可以并发多条线程,每条线程并行执行不同的任 务。
线程与进程的关系可以归结于以下几点:
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个主线程;
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源;
3、线程作为调度和分配的基本单位,进程作为拥有资源的基本单位;
4、进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的 资源;
5、在创建或撤消进程时,由于系统都要为之分配和回收资源,导致系统的开销大于创 建或撤消线程时的开销
多进程程序结构和多线程程序结构有很大的不同,多线程程序结构相对以多进程程序结 构有以下的优势
(1)方便的通信和数据交换
(2)更高效的利用 CPU
(3)模块化的编程,能更清晰的表达程序中独立事情的关系,结构清晰
线程的一些术语
并发:并发是指在同一时刻,只能有一条指令执行,但多条进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。(看起来同时发生,但是是单核)
并行:并行是指在同一时刻,有多条指令在多个处理器上同时执行(真正的同时发生)
同步:彼此有依赖关系的调用不应该“同时发生”,而同步就是要阻止那些“同时发生”的事情
异步:异步和同步的概念相对的,任何两个彼此独立的操作是异步的,它表明事情独立的发生
线程管理
线程管理包含了线程的创建、终止、等待、分离、设置属性等操作
线程 ID
Pthreads 线程有一个 pthread_t 类型的 ID 来引用。线程可以通过调用 pthread_self()函数 来获取自己的 ID。pthread_self()函数原型如下
pthread_t pthread_self(void);
该函数返回调用线程的线程 ID
pthread_self() 线程的ID
#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
int main()
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("pid is %u, tid is %u\n",pid,tid);
return 0;
}
创建线程
- 在进程中创建一个新线程的函数是 pthread_create(),原型如下
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
参数说明:
1、 thread 用指向新创建的线程的 ID;
2、attr 用来表示一个封装了线程各种属性的属性对象,如果 attr 为 NULL,新线程就 使用默认的属性,13.3.4 节将讨论线程属性的细节;
3、start_routine 是线程开始执行的时候调用的函数的名字,start_routine 函数有一个有 指向 void 的指针参数,并有 pthread_create 的第四个参数 arg 指定值,同时 start_routine 函数返回一个指向 void 的指针,这个返回值被 pthread_join 当做退出 状态处理,13.3.3 节介绍线程的退出状态;
4、arg 为参数 start_routine 指定函数的参数。
返回值说明:
1、如果 pthread_create()调用成功,函数返回 0,否则返回一个非 0 的错误码
/*
getpid() 获取进程ID
pthread_self() 获取线程ID
int pthread_create(新线程ID,新线程属性 默认为NULL,新线程到启动函数,传给新线程),
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<pthread.h>
void print_id(char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid is %u,tid is %u\n",s,pid,tid);
}
void *thread_fun(void *arg)
{
print_id(arg);
return (void *)0;
}
int main()
{
pthread_t ntid;
int err;
err = pthread_create(&ntid,NULL,thread_fun,"new_rhread\n");
if(err != 0)
{
printf("creat new thread failure\n");
return 0;
}
print_id("main thread");
sleep(2);
return 0;
}
这里创造了一个线程和进程,并打印线程和进程的ID
初始化线程/主线程
1、当C程序运行时,首先运行main函数,在线程代码中,这个特殊的执行流被称作为初始化线程或者主线程,你可以在初始化线程中做任何普通线程可以做的事情
2、主线程的特殊在于,它在main函数返回的时候,会导致进程结束,进程内所有的线程也将会结束。这可不是一个好现象,你可以在主线程中调用pthread_exit函数,这样进程就会等待所有线程结束才会终止。
3、在大多情况下,主线程在默认堆栈上运行,这个堆栈可以增长到足够的长度,而普通线程的堆栈是受限制的,一旦一处就会产生错误。
4、主线程参数的方式是通过argc和argv,而普通的线程只有一个参数void
/*
getpid() 获取进程ID
pthread_self() 获取线程ID
int pthread_create(新线程ID,新线程属性 默认为NULL,新线程到启动函数,传给新线程),
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<pthread.h>
struct student
{
int age;
char name[20];
};
//初始化线程的启动函数,格式要符合第三个参数void *(start_routine)(void *)
void *thread_fun(void *stu) //新线程
{
printf("student age is %d,name is %s",((struct student *)stu)->age,((struct student *)stu)->name);//这里输出不能直接引用结构体里面的变量,所以要强制转化
return (void *)0;
}
int main(int argc,char *argv[])
{
pthread_t ntid;
int err;
struct student stu;
stu.age = 20;
int i;
memcpy(stu.name,"zhanhan\n",20);//不能直接赋予结构体,而应该用memcpy给结构体初始化
err = pthread_create(&ntid,NULL,thread_fun,(void *)(&stu));
if(err != 0)
{
printf("creat new thread failure\n");
return 0;
}
printf("main thread %d\n",argc);//打印参数的个数
for(i = 0; i < argc; i++)
{
printf("main thread argc is %s\n",argv[i]);//打印出给出的字符串
}
sleep(1);//睡眠一秒,确保线程的运行 如果不休眠,则新线程的东西不回打印出来
return 0;
//这里的return 0 可以改成pthread_exit(rval); rval自动回填,要定义 int *ravl
}
- 进程的终止可以通过直接调用 exit()、执行 main()中的 return、或者通过进程的某个其它 线程调用 exit()来实现。在以上任何一种情况下,所有的线程都会终止。如果主线程在创建 了其它线程后没有任务需要处理,那么它应该阻塞等待所有线程都结束为止,或者应该调用 pthread_exit(NULL)。 调用 exit()函数会使整个进程终止,而调用 pthread_exit()只会使得调用线程终止,同时 在创建的线程的顶层执行 return 线程会隐式地调用 pthread_exit()。pthread_exit()函数原型如 下:
void pthread_exit(void *retval);
retval 是一个 void 类型的指针,可以将线程的返回值当作 pthread_exit()的参数传入,这 个值同样被 pthread_join()当作退出状态处理。如果进程的最后一个线程调用了 pthread_exit(), 进程会带着状态返回值 0 退出。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<pthread.h>
void *thread_fun(void *arg)//3种方式退出
{
//如果传入参数1,采用return退出
if(strcmp("1",(char *)arg) == 0)
{
printf("new thread return\n");
return (void *)1;
}
//如果传入参数2,采用Pthread_exit方式退出
if(strcmp("2",(char *)arg) == 0)
{
printf("new thread prhread_exit!\n");
pthread_exit((void *)2);
}
//如果传入参数3,采用exit方式退出
if(strcmp("3",(char *)arg) == 0)
{
printf("new thread exit\n");
exit(3);
}
}
int main(int argc,char *argv[])
{
pthread_t tid;
int err;
err = pthread_create(&tid,NULL,thread_fun,(void *)argv[1]);
if(err != 0)
{
printf("creat new thread failure\n");
return 0;
}
sleep(1);//睡眠一秒,确保线程的运行
printf("main thread\n");
return 0;
}
方式1和方式2都不会导致主线程退出,而exit会导致主线程退出
2、证明任意一个线程调用exit函数都会导致进程退出
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<sys/ipc.h>
int num;
void *thread_fun(void *arg)
{
printf("I'm new thread: %d\n", (int *)arg);
//如果线程到参数arg和产生到随机数相同,那么线程就采用exit方式退出
if(num == (int *)arg)
{
printf("new thread %d exit!!!\n", (int *)arg);
exit(0);
}
//否则线程睡眠,让其他线程执行
sleep(2);
pthread_exit((void *)0);
}
int main()
{
int err;
pthread_t tid;
//产生随机种子
srand((unsigned int)time(NULL));
//产生一个10以内到随机数
num = rand()%11;
int i=10;
//创建10个线程,给每个线程传递参数i
while(i--)
{
err = pthread_create(&tid, NULL, thread_fun, (void *)i);
if(err != 0)
{
printf("create new thread failed\n");
return 0;
}
}
//睡眠1s,让新线程先运行
sleep(1);
return 0;
}
线程的连接
如果一个线程是非分离线程,那么其它线程可调用 pthread_join()函数对非分离线程进行 连接。pthread_join()函数原型如下
int pthread_join(pthread_t tid, void **retval);
函数的参数
参数 retval 为指向线程的返回值的指针提供一个位置,这个返回值是目标线程调用 pthread_exit()或者 return 所提供的值。当目标线程无需返回时可使用 NULL 值,调用线程如 果不需对目标线程的返回状态进行检查可直接将 retval 赋值为 NULL
pthread_join()函数将调用线程挂起,直到第一个参数thread指定目标线程终止运行为止。
函数的返回值:如果 pthread_join()成功调用,它将返回 0 值,如果不成功,pthread_join()返回一个非 0 的错误码
线程的分离
-
pthread_detach()函数可以将非分离线程设置为分离线程,函数原型如下
-
int pthread_detach(pthread_t tid);
-
参数 thread 是要分离的线程的 ID。 线程可以自己来设置分离,也可以由其它线程来设置分离,以下代码线程可设置自身分 离:
pthread_detach(pthread_self());
成功返回 0;失败返回一个非 0 的错误码,
写例子
/*
pthread_join(线程ID,返回值)
pthreaddetach(线程ID)
*/
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
void *thread_fun1(void *arg)//线程1
{
printf("this is first thread\n");
return (void *)1;
}
void *thread_fun2(void *arg)//线程2
{
printf("this is second thread\n");
//pthread_detach(pthread_self());//分离线程2
pthread_exit((void *)2);
}
int main(int argc, char *argv[])
{
int err1, err2;
pthread_t tid1, tid2;
void *rval1, *rval2;
int ret1, ret2;
err1 = pthread_create(&tid1,NULL, thread_fun1,NULL);
err2 = pthread_create(&tid2,NULL, thread_fun2,NULL);
printf("this is main thread\n");
if(err1 || err2)
{
printf("create new thread failure\n");
return 0;
}
ret1 = pthread_join(tid1,&rval1);//线程的连接
ret2 = pthread_join(tid2,&rval2);
printf("tid1 is %d\n",ret1);
printf("tid2 is %d\n",ret2);
printf("thread1 exit code is %d\n",(int *)rval1);//打印rvall的返回值
printf("thread2 exit code is %d\n",(int *)rval2);
}
线程的取消
int pthread_cancel(pthread_t tid)
取消tid指定线程,成功返回0,但是取消只是发送一个请求,并不意味着等待线程终止,而且发送成功也不意味着tid一定会终止