程序、进程和线程

程序是指令和数据的有序集合。它是一个静态概念。而进程是运行中的程序,是一个动态概念。一个程序要想运行,必须由操作系统为它分配资源,变为进程,也就是”活动的程序“。 进程是操作系统资源分配的最小单位,而线程是程序执行的最小单位,操作系统的调度也是以线程为单位的,一个进程可以拥有多个线程。

由于虚拟内存技术,不同的进程的地址空间都是独立的,防止了进程之间的干扰,导致进程的数据被篡改,使得进程异常崩溃。但也是由于这种安全保护机制,使得进程之间进行通信变得非常麻烦。

相对于进程。线程并不独立于其他进程,因此线程与其他线程共享其代码段、数据段和操作系统资源(如打开的文件和信号)。但是,与进程一样,线程有自己的程序计数器(PC)、寄存器集和堆栈空间。因此线程共享数据非常方便,并且具有良好的并发性。

多线程的相关函数

创建线程

创建线程的函数原型:

1
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);

如果线程创建成功,就会返回0,否则返回错误号。创建线程需要四个参数:

  • 线程id:第一个参数是pthread_t类型的变量,它本质上是一个无符号整数,需要在创建线程之前定义,作为线程的标识符。
  • 线程属性:该参数可以设置线程的优先级,初始栈大小等属性,如果传入NULL,线程启动时会使用默认的参数。
  • 函数指针:第三个参数是线程启动时要执行的函数的指针,也就是函数名。
  • 线程函数参数:线程启动时要执行的函数可以传入参数,如果有多个参数可以定义一个结构体,将参数都放入结构体中,再传入结构体指针,需要注意的是一定要把结构体指针转换为void *类型的指针,在线程函数内部再转换为结构体指针类型。

线程退出

函数原型:

1
void pthread_exit(void *retval);

该函数可以终止一个线程,线程的返回状态通过指针来进行存储。

线程接合(join)和分离(detach)

线程接合

创建多个线程后,线程之间执行的先后顺序由操作系统决定。但是往往实际的编程中,很多时候需要对线程的行为进行控制,即存在一个主控线程,主控线程创建新线程去执行其他任务。所以,为了让主控线程可以得到子线程的执行结果,需要让主控线程等待子线程执行结束。否则,线程的执行完全由操作系统来调度,很有可能主线程执行完毕退出,而子线程还没有执行完,这样的话线程之间就无法相互配合协作。

线程等待函数原型:

1
int pthread_join(pthread_t thread, void **retval);

该函数可以等待一个指定的线程结束,并且通过retval来得到要等待的线程的返回值。

线程分离

另一种线程类型就是以分离方式来启动线程。分离类型的线程独立于主控线程,线程结束后自动释放资源。分离类型的线程一般是用于子线程执行的相关信息返回到主线程的场景。 有三种方式可以将线程设为detach类型。

  • 创建线程时,指定线程的属性:pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  • 通过子线程调用pthread_detach(pthread_self())
  • 在主控线程中调用pthread_detach(thread_id)函数,传入的参数为要设置detach属性的线程id

detach函数原型:

1
int pthread_detach(pthread_t thread);

如果调用成功,返回值为0,否则返回错误号。

代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void *threadFunction(void *arg)
{
    printf("the argumemt is:%s\n",(char *)arg);
    return (void *)0;
}
int main()
{
    pthread_t th1,th2;
    int ret1=0,ret2=0;
    char *arg1="i am thread 1";
    char *arg2="i am thread 2";
    void *retVal1;

    ret1=pthread_create(&th1,NULL,threadFunction,(void *)arg1);
    ret2=pthread_create(&th2,NULL,threadFunction,(void *)arg2);

    if(ret1==0)
        printf("thread1 create success\n");
    else
        perror("thread1 create failed");
    if(ret2==0)
        printf("thread2 create success\n");
    else
        perror("thread2 create failed");
    pthread_detach(th2);
    pthread_join(th1,&retVal1);
    printf("thread 1 exit status:%d\n",retVal1);
    //sleep(1);
    return 0;
}

以上程序设置了开启了两个新线程,其中th1设置join类型,所以th1线程一定会打印出结果,th2detach类型,在实际运行时,th2线程有可能还没执行,主线程就退出了,如果让主线程sleep,等待th2线程,那么可以看到th2线程的执行结果。

需要注意的是,pthread_join函数的第二个参数要求是二级指针,但是指针本质上就是一个变量,它存储的可以是内存地址,也可以是其他内容。所以这里直接定义一个指针,传入指针的地址,pthread_join函数会给传入的指针指向的地址写入线程的退出状态。然后,在主线程中,直接以%d的形式打印出指针变量的值。

执行结果:

1
2
3
4
the argumemt is:i am thread 1
thread1 create success
thread2 create success
thread 1 exit status:0