Bootstrap

系统编程-线程

线程

        与进程类似,线程(thread)是允许程序并发执行多任务的一种机制。一个进程可以包含多个线程,每个线程均会独立执行相同的程序,且共享同一份全局内存区域。其中包括初始化数据段、未初始化数据段,以及堆内存段

        同一进程中的多个线程可以并发执行。在多处理器环境下,多个线程可以同时并行。如果一个线程因等待 I/O 操作而遭阻塞,那么其他线程依然可以继续运行

        进程和线程都可以实现多块代码同时执行

        线程相对进程的优势:

  1. 创建进程的代价大:创建进程会复制父进程的内容,需要额外分配资源,可能会造成资源的浪费;线程比进程的创建要快很多
  2. 进程间难以共享:进程间的信息难以共享,需要进行进程间的通信;线程间能方便、快速的共享信息,不过要避免多个线程同时修改同一数据的情况

创建线程

        启动程序时,产生的进程只有单条线程,称之为初始(initial)或主(main)线程。函数 pthread_create() 负责创建一条新线程。新线程通过调用带有参数 arg 的函数 start_routine(即 start_routine(arg))而开始执行。调用 pthread_create() 的线程会继续执行该调用之后的语句。
进程内部的每个线程都有一个唯一标识,称为线程 ID。线程获取自己的线程 ID 使用pthread_self() 函数。

使用命令 ps -eLf 查看线程
ubuntu20 编译有线程的代码可能失败,使用 gcc 编译,加参数 -lpthread

pthread_self() 函数 

函数描述:

        获取线程 ID。类似于进程中的 getpid() 函数。线程 ID 是进程内部的识别标志(两个进程间,允许线程 ID 相同)

函数原型:

        pthread_t pthread_self(void); 

函数返回值:

        成功:返回线程 ID,pthread_t 为无符号整型(%lu)

        失败:无

pthread_create() 函数

函数描述:

        创建一个新线程。 类似进程中的 fork() 函数。

头文件:

        #include <pthread.h>

函数原型:

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

函数参数:

        thread:传出参数,保存系统为我们分配好的线程 ID

        attr:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。

        start_routine:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束

        arg:线程主函数的参数

函数返回值:

        成功:返回0

        失败:返回错误号

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

void* thread1(void* arg)
{
    printf("thread1 tid = %lu\n", pthread_self());
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    printf("main tid = %lu\n", pthread_self());
    while(1);
    
    return 0;
}

线程间可以共享全局变量局部变量只能通过传参共享 (多个局部变量,如果同类型传数组,不同类型传结构体)

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    printf("thread1 tid = %lu\n", pthread_self());
    a = 1;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    printf("main tid = %lu\n", pthread_self());
    sleep(1);
    printf("a = %d\n", a);
    while(1);
    
    return 0;
}
#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

void* thread1(void* arg)
{
    printf("b = %d\n", *(int*)arg);
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    int b = 1;
    pthread_create(&tid, NULL, thread1, &b);
    printf("main tid = %lu\n", pthread_self());
    while(1);
    
    return 0;
}

终止线程

终止线程的方式:

  1. 线程 start_routine 函数执行 return。
  2. 线程调用 pthread_exit() 终止线程。
  3. 任意线程调用了 exit(),或者主线程执行了 return 语句(在 main()函数中),都会导致进程中的所有线程立即终止
  4. 调用 pthread_cancel(thread) 取消指定线程

pthread_exit() 函数将终止调用线程,且其返回值可由另一线程通过调用 pthread_join() 来获取

调用 pthread_exit() 相当于在线程的 start_routine 函数中执行 return,不同之处在于,可在线程 start_routine 函数所调用的任意函数中调用 pthread_exit() 都能够直接终止线程 。

pthread_exit() 函数

函数描述:

        终止线程

函数原型:

        void pthread_exit(void *retval);

函数参数:

        retval:表示线程退出状态,通常传NULL

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    while(a < 10)
    {
        printf("a = %d\n", a++);
        sleep(1);
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    sleep(3);
    
    return 0;  //进程结束,进程中包含线程,故线程也结束
}

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    while(a < 10)
    {
        printf("a = %d\n", a++);
        sleep(1);
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    sleep(3);
    exit(0);  //结束进程的函数,同上
    
    return 0;
}

 

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    while(a < 10)
    {
        printf("a = %d\n", a++);
        sleep(1);
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    sleep(3);
    pthread_exit(NULL);  //结束线程的函数,只将主线程结束,子线程继续运行
    
    return 0;
}

pthread_cancel() 函数

函数描述:

        向指定线程发送一个取消请求。发出取消请求后,函数 pthread_cancel() 当即返回,不会等待目标线程的退出。被请求取消的线程不会立即取消,需要等待到达某个取消点取消点通常是一些系统调用,也可以使用pthread_testcancel()函数手动创建一个取消点,被取消的线程返回值是PTHREAD_CANCELED(-1)。

函数原型:

        int pthread_cancel(pthread_t thread);

函数参数:

        thread:要取消的线程ID

函数返回值:

        成功:返回0

        失败:返回错误号

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    while(1)
    {
        if(a == 10)
        {
            a = 0;
        }
        a++;
        pthread_testcancel();  //设置取消点
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    
    int ret = pthread_cancel(tid);
    if(ret != 0)
    {
        printf("pthread_cancel error, ret = %d\n", ret);
    }
    else
    {
        printf("pthread_cancel success, rret = %d\n", ret);
    }
    
    while(1)
    {
        printf("a = %d\n", a);
        sleep(1);
    }
    
    return 0;
}

线程的连接与分离

pthread_join() 函数

函数描述:

        等待指定线程终止并回收,这种操作叫做连接(joining)。未连接的线程会产生僵尸线程,类似僵尸进程的概念。

函数原型:

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

函数参数: 

        thread:要等待的线程ID

        retval:传出参数,存储线程返回值

函数返回值:

        成功:返回0

        失败:返回错误号

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    sleep(5);
    return (void*)"abc";
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);

    void* ret;
    pthread_join(tid, &ret);
    printf("retval = %s\n", (char*)ret);

    return 0;
}

 

pthread_detach() 函数

函数描述:

        默认线程是可连接的(joinable),当线程退出时,其他线程可以通过调用 pthread_join() 获取其返回状态。有时,程序员并不关心线程的返回状态,只是希望系统在线程终止时能够自动清理并移除。这时可以调用 pthread_detach() 函数,将线程标记为分离(detached)状态。

函数原型:

        int pthread_detach(pthread_t thread);

函数参数:

        thread:要分离的线程ID

        retval:存储线程返回值

函数返回值:

        成功:返回0

        失败:返回错误号

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    sleep(5);
    return (void*)"abc";
}

int main(int argc, char* argv[])
{
    pthread_t tid;
    pthread_create(&tid, NULL, thread1, NULL);
    pthread_detach(tid);

    sleep(10);
    
    void* ret;
    pthread_join(tid, &ret);
    printf("retval = %s\n", (char*)ret);

    return 0;
}

练习

        定义一个全局变量a,创建10个线程,每个线程对a进行自增(a++)1万次,所有线程自增结束主线程打印a的值。

#include <stdio.h>
#include <string.h>                                                             
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <mqueue.h>

int a = 0;

void* thread1(void* arg)
{
    for(int i = 0; i < 10000; i++)
    {
        a++;
    }
    return NULL;
}

int main(int argc, char* argv[])
{
    pthread_t tid[10];
    for(int i = 0; i < 10; i++)
    {
        pthread_create(&tid[i], NULL, thread1, NULL);
    }
    for(int i = 0; i < 10; i++)
    {
        pthread_join(tid[i], NULL);
    }
    printf("a = %d\n", a);

    return 0;
}
;