1 什么是多线程?
多线程是现代操作系统所支持的一种重要的多任务机制,一个线程进行系统调用被阻塞不会影响同一进程中的其他线程,Linux下可以使用POSIX标准所规定的API接口实现多线程编程。
在介绍线程前先用生活中最常见的一个小例子来理解什么是线程:
假如你去一家餐馆吃饭,那家餐馆只有一个服务员,所以这个唯一的服务员给你点菜的时候,别的去餐馆吃饭的人就得等着。但是如果这个餐馆有多个服务员的话,那么在同一时刻就可以给多个去餐馆吃饭的人点菜,这里举A、B两个服务员,分别接待甲、乙两个顾客,而每个顾客点了不同的三道菜。
上面例子中的餐馆可以理解成一个程序,而A、B两个服务员可以理解成两个线程,后厨做菜的厨师可以看做是CPU(假设只有一个CPU)。
从A、B两个服务员同时接待甲、乙两个顾客这个表象来看线程是“同步”、“并发”执行的,但是在厨师做菜的过程中还是有先后之分的,只是厨师会把甲乙两个顾客点的菜分开来做,做完甲的一道菜后立刻开始做乙的一道菜,这样不停地切换着做甲乙两个顾客点的菜。而在甲乙顾客看来他们桌上都有着菜吃,误以为他们的菜是同时做出来的。
而计算机里的多线程也是如此,cpu会分配给每一个线程只有极少的运行时间,时间一到就交出运行权,所有线程被快速的切换执行,因为cpu的执行速度非常的快,所以在执行的过程中我们可以简单的认为这些线程是“并发”执行的。
上面厨师做菜的原则是:不同的切换顾客们点的菜。假设厨师做菜的原则变了,是单顾客原则:给一位顾客做完所有的菜后再给别的顾客做菜,就是说先一下子把甲点的三道菜全做出来后再去做乙点的那三道菜,这样的话如果厨师在做甲的某一道菜时发现甲点的那道菜的原料没有了,那么乙的菜就得一直等着。
如果是在计算机中,单顾客原则执行线程的话,当一个恶性的线程运行不下去时,计算机就会出现死机的现象,这时候只能重启。
上面介绍的厨师做菜的两种原则分别代表着计算机中线程的两种调度形式:抢占式调度和非抢占式调度。应用中我们一般用抢占式调度(即厨师的第一种做菜原则)。
2 为什么要用多线程?
首先说下什么是进程,C、C++、Java等语言编写的源程序经相应的编译器编译成可执行文件后,提交给计算机处理器运行。这时,处在可执行状态中的应用程序称为进程。从用户角度来看,进程是应用程序的一个执行过程。从操作系统核心角度来看,进程代表的是操作系统分配的内存、CPU时间片等资源的基本单位,是为正在运行的程序提供的运行环境。
和进程相比,采用多线程可以节约系统资源。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间(快10到100倍)
线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
3 Linux环境下多线程函数
3.1 线程创建
在进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用pthread_create函数:
#include <pthread.h>
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
参数说明:
- thread:指向线程标识符(也叫线程句柄)的指针。
- attr:用于设置线程的属性,一般不需要特殊的属性,所以可以简单地设置为NULL。
- start_routine:传递新线程所要执行的函数地址。
- arg:新线程所要执行的函数的参数。
返回值:调用如果成功,则返回值是0;如果失败则返回错误代码。
1.头文件必须要引用pthread.h文件,此文件我也不知道在哪里,linux内核源码中是找不到了,或许在编译器中吧;
2.编译时必须要链接pthread库,如:
“gcc -o main main.c -lpthread”
3.2 线程退出
线程的退出方式有三种:
(1)执行完成后隐式退出;
(2)由线程本身显示调用pthread_exit 函数退出;
pthread_exit (void * retval);
(3)被其他线程用pthread_cance函数终止:
pthread_cancel (pthread_t thread);
函数pthread_join用来等待一个线程的结束,线程间同步的操作,它以阻塞的方式等待thread指定的线程结束。当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。并且thread指定的线程必须是joinable的。
pthread_join (pthread_t thread, void** threadreturn);
一个简单Linux多线程实例
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void *thread_function(void *arg);
char message[] = "Hello World";
int main()
{
int res;
pthread_t a_thread;// 用于声明线程ID,原型:typedef unsigned long int pthread_t;
void* thread_result;
res = pthread_create(&a_thread, NULL, thread_function, (void *)message);
if (res != 0)
{
perror("Thread creation failed!");
exit(EXIT_FAILURE);
}
printf("Waiting for thread to finish.../n");
res = pthread_join(a_thread, &thread_result);
if (res != 0)
{
perror("Thread join failed!/n");
exit(EXIT_FAILURE);
}
printf("Thread joined, it returned %s/n", (char *)thread_result);
printf("Message is now %s/n", message);
exit(EXIT_FAILURE);
}
void *thread_function(void *arg)
{
printf("thread_function is running. Argument was %s/n", (char *)arg);
sleep(3);
strcpy(message, "Bye!");
pthread_exit("Thank you for your CPU time!");
}