分析多进程和多线程两种编程模型的优势和劣势
并发和并行和串行
⚫ 串行:一件事、一件事接着做
⚫ 并发:交替做不同的事;
串行
相比于串行和并行,并发强调的是一种时分复用,与串行的区别在于,它不必等待上一个任务完成之后在做下一个任务,可以打断当前执行的任务切换执行下一个任何,这就是时分复用。在同一个执行单元上,将时间分解成不同的片段(时间片),每个任务执行一段时间,时间一到则切换执行下一个任务,依次这样轮训(交叉/交替执行),这就是并发运行。
线程 ID(重要)
#include <pthread.h>pthread_t pthread_self(void);使用该函数需要包含头文件 <pthread.h> 。
#include <pthread.h>int pthread_equal(pthread_t t1, pthread_t t2);
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);使用该函数需要包含头文件 <pthread.h> 。thread :pthread_t 类型指针,当 pthread_create() 成功返回时,新创建的线程的线程 ID 会保存在参数 thread 所指向的内存中,后续的线程相关函数会使用该标识来引用此线程。attr : pthread_attr_t 类型指针,指向 pthread_attr_t 类型的缓冲区, pthread_attr_t 数据类型定义了线程的各种属性,关于线程属性将会在 11.8 小节介绍。如果将参数 attr 设置为 NULL ,那么表示将线程的所有属性设置为默认值,以此创建新线程。start_routine : 参数 start_routine 是一个函数指针,指向一个函数,新创建的线程从 start_routine() 函数开始运行,该函数返回值类型为void * ,并且该函数的参数只有一个 void * ,其实这个参数就是 pthread_create() 函数的第四个参数 arg 。如果需要向 start_routine() 传递的参数有一个以上,那么需要把这些参数放到一个结构体中,然后把这个结构体对象的地址作为 arg 参数传入。arg : 传递给 start_routine() 函数的参数。一般情况下,需要将 arg 指向一个全局或堆变量,意思就是说在线程的生命周期中,该 arg 指向的对象必须存在,否则如果线程中访问了该对象将会出现错误。当然也可将参数 arg 设置为 NULL ,表示不需要传入参数给 start_routine() 函数。返回值: 成功返回 0 ;失败时将返回一个错误号,并且参数 thread 指向的内容是不确定的。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
return (void *)0;
}
int main(void)
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "Error: %s\n", strerror(ret));
exit(-1);
}
printf("主线程: 进程 ID<%d> 线程 ID<%lu>\n", getpid(), pthread_self());
sleep(1);
exit(0);
}
pthread_exit()函数
#include <pthread.h>void pthread_exit(void *retval);使用该函数需要包含头文件 <pthread.h> 。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程 start\n");
sleep(1);
printf("新线程 end\n");
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "Error: %s\n", strerror(ret));
exit(-1);
}
printf("主线程 end\n");
pthread_exit(NULL);
exit(0);
}
回收线程
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);使用该函数需要包含头文件 <pthread.h> 。thread : pthread_join() 等待指定线程的终止,通过参数 thread (线程 ID )指定需要等待的线程;retval : 如果参数 retval 不为 NULL ,则 pthread_join() 将目标线程的退出状态(即目标线程通过 pthread_exit()退出时指定的返回值或者在线程 start 函数中执行 return 语句对应的返回值)复制到 *retval 所指向的内存区域;如果目标线程被 pthread_cancel() 取消,则将 PTHREAD_CANCELED 放在 *retval 中。如果对目标线程的终止状态不感兴趣,则可将参数 retval 设置为 NULL 。返回值: 成功返回 0 ;失败将返回错误码。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程 start\n");
sleep(2);
printf("新线程 end\n");
pthread_exit((void *)10);
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
#include <pthread.h>int pthread_cancel(pthread_t thread);使用该函数需要包含头文件 <pthread.h> ,参数 thread 指定需要取消的目标线程ID;成功返回 0 ,失败将返回错误码。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
printf("新线程--running\n");
for ( ; ; )
sleep(1);
return (void *)0;
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
/* 创建新线程 */
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
/* 向新线程发送取消请求 */
ret = pthread_cancel(tid);
if (ret) {
fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
exit(-1);
}
/* 等待新线程终止 回收线程*/
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
取消状态以及类型
默认情况下,线程是被动响应其它线程发送过来的取消请求的,响应请求然后退出线程。当然,线程可以主动选择不被取消或者控制如何被取消,通过 pthread_setcancelstate()和pthread_setcanceltype()来设置线程的取消性状态和类型。
#include <pthread.h>int pthread_setcancelstate(int state, int *oldstate);int pthread_setcanceltype(int type, int *oldtype);使用这些函数需要包含头文件 <pthread.h> ,
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
/* 设置为不可被取消 */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
for ( ; ; ) {
printf("新线程--running\n");
sleep(2);
}
return (void *)0;
}
int main(void)
{
pthread_t tid;
void *tret;
int ret;
/* 创建新线程 */
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1);
/* 向新线程发送取消请求 */
ret = pthread_cancel(tid);
if (ret) {
fprintf(stderr, "pthread_cancel error: %s\n", strerror(ret));
exit(-1);
}
/* 等待新线程终止 ,但实际上无法中止新线程*/
ret = pthread_join(tid, &tret);
if (ret) {
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
exit(-1);
}
printf("新线程终止, code=%ld\n", (long)tret);
exit(0);
}
#include <pthread.h>int pthread_detach(pthread_t thread);使用该函数需要包含头文件 <pthread.h>
一个线程既可以将另一个线程分离,同时也可以将自己分离
pthread_detach(pthread_self());
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
static void *new_thread_start(void *arg)
{
int ret;
/* 自行分离 */
ret = pthread_detach(pthread_self());
if (ret) {
fprintf(stderr, "pthread_detach error: %s\n", strerror(ret));
return NULL;
}
printf("新线程 start\n");
sleep(2); //休眠 2 秒钟
printf("新线程 end\n");
pthread_exit(NULL);
}
int main(void)
{
pthread_t tid;
int ret;
/* 创建新线程 */
ret = pthread_create(&tid, NULL, new_thread_start, NULL);
if (ret) {
fprintf(stderr, "pthread_create error: %s\n", strerror(ret));
exit(-1);
}
sleep(1); //休眠 1 秒钟
/* 等待新线程终止 */
ret = pthread_join(tid, NULL);
if (ret)
fprintf(stderr, "pthread_join error: %s\n", strerror(ret));
pthread_exit(NULL);
}
注册线程清理处理函数(重要)
#include <pthread.h>void pthread_cleanup_push(void (*routine)(void *), void *arg);void pthread_cleanup_pop(int execute);使用这些函数需要包含头文件 <pthread.h> 。
线程属性
如前所述,调用 pthread_create()创建线程,可对新建线程的各种属性进行设置。在 Linux 下,使用pthread_attr_t 数据类型定义线程的所有属性
#include <pthread.h>int pthread_attr_init(pthread_attr_t *attr);int pthread_attr_destroy(pthread_attr_t *attr);使用这些函数需要包含头文件 <pthread.h>
线程栈属性
每个线程都有自己的栈空间,pthread_attr_t 数据结构中定义了栈的起始地址以及栈大小,调用函数 pthread_attr_getstack()可以获取这些信息,函数 pthread_attr_setstack()对栈起始地址和栈大小进行设置,其函数原型如下所示:
#include <pthread.h>int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize);int pthread_attr_getstack(const pthread_attr_t *attr, void **stackaddr, size_t *stacksize);使用这些函数需要包含头文件 <pthread.h>函数 pthread_attr_getstack() ,参数和返回值含义如下attr : 参数 attr 指向线程属性对象。stackaddr : 调用 pthread_attr_getstack() 可获取栈起始地址,并将起始地址信息保存在 *stackaddr 中;stacksize : 调用 pthread_attr_getstack() 可获取栈大小,并将栈大小信息保存在参数 stacksize 所指向的内存中;返回值: 成功返回 0 ,失败将返回一个非 0 值的错误码。函数 pthread_attr_setstack() ,参数和返回值含义如下:attr : 参数 attr 指向线程属性对象。stackaddr : 设置栈起始地址为指定值。stacksize : 设置栈大小为指定值;返回值: 成功返回 0 ,失败将返回一个非 0 值的错误码。
线程安全
==================================================================================================================================================