这篇文章主要是为了帮助大家熟悉 POSIX 线程库以及在实际开发中使用它的特性。我们会具体讲解如何利用这个线程库定义的不同工具
来解决编程中的问题。当然这里隐含了一个假设,就是读者已经了解过并行编程(或者多进程)的相关概念,如果没有这些背景知识
的话,读者可能会感觉到很难理解。不过也没关系,我的另一篇教程里边有专门为只具备线性编程思维的读者提供了对并行编程理论
和相关术语的讲解。
同样的,我假设聪明的你已经熟悉了异步编程模型,那些经常使用桌面环境的人会更容易去接受多线程编程的理念。
当我们谈到 POSIX 线程的时候,肯定会有人心生疑惑:“我们应该使用哪个标准下的 POSIX 线程?”。由于 POSIX 线程标准已经修订了好
多年,人们会发现,依据不同标准的实现有不同的函数集,不同的默认值和不同的细微差别。所以在此说明的是,本教程使用的是 v0.5 版的 Linux 内核中的线程库,使用其他操作系统和使用其他版本的读者,需要阅读一下你们对应的系统文档来同本文中的实例进行对应。同时,有些示例代码中使用到了阻塞式的系统调用,它们不能再用户级的线程库中很好的工作(参考另一篇文章:parallel programming theory tutorial 来获取详细信息)。好了,说了那么多,主要是为了能保证文章中的示例代码能够在其他系统中正常使用,从而提高跨平台性。
线程是一个迷你版的进程,它们拥有自己的栈,能够执行给定的一段代码。但是不同于进程的是,线程通常与其他线程共享记忆体(而
每个进程都拥有一个独立的记忆体区域)。一个线程组就是一个执行相同代码的线程的集合,他们共用记忆体,可以访问相同的全局变
量,拥有同样的文件描述符等等,他们以并行的方式执行(可能是时间片的方式,或者对于多核心系统,他们会真正平行执行)。
使用线程组而不是普通顺序执行程序的好处是多个操作可以同时进行,当一些事件产生的时候,他们能立马被处理(例如:如果我们有
一个线程处理用户接口,另一个线程处理数据库查询,那么我们可以在处理很多用户查询的同时,依然能够响应用户的输入)。
使用线程组而不是进程组的好处是线程间的上下文切换要比进程间的上下文切换要快很多(上下文切换是指系统从一个正在运行的线程或进程切换到去执行另一个线程或进程)。此外,线程间的通信也远远比进程间通信要高效很多。
线程编程有利也有弊,由于线程组共享记忆体,如果一个线程破坏了记忆体,那么其他线程也要受到牵连。但是进程就不同了,操作系
统会将进程之间隔离开,如果一个进程破坏了它的记忆体,那么其他进程不会受到影响。使用进程的另一个好处是,不同的进程可以运
行在不同的机器上,但是线程必须运行在同一台机器上(至少通常情况下是这样的)。
当一个多线程程序启动执行到main()函数的时候,就会有一个线程运行,这是一个全程线程(full-fledged thread,或者叫主线程),如果想创建一个新的线程,程序中需要使用pthread_create()函数
#include <stdio.h>
#include <pthread.h>
void *do_loop(void *data)
{
int i; // counter, to print numbers
int j; // counter, for delay
int me = *((int *)data);
for (i = 0; i < 10; i++) {
for (j = 0; j < 50000; j++) // delay loop
;
printf("'%d' - Got '%d'\n", me, i);
}
// terminate the thread
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
int thr_id; // thread ID for the newly created thread
pthread_t p_thread; // thread's structure
int a = 1; // thread 1 identifying number
int b = 2; // thread 2 identifying number
// create a new thread that will execute 'do_loop()'
thr_id = pthread_create(&p_thread, NULL, do_loop, (void *)&a);
// run 'do_loop' in the main thread as well
do_loop((void *)&b);
return 0;
}
上述这段代码需要特殊说明的是: