Linux下多线程教程

来源:互联网 发布:淘宝如何抢秒杀商品 编辑:程序博客网 时间:2024/06/05 04:50

所有的Unix类系统都是多任务操作系统。unix允许一个用户同时运行多个进程,这是我们今天使用的最强大、最灵活的多任务编程模型之一。

在传统的unix模式,进程都是通过fork()系统调用创建的。

Fork()产生一个当前进程的拷贝。在子进程中fork()返回1,在父进程中fork返回子进程的pid。fork()通常使用方法:

[do parent stuff]ppid = fork ();if (ppid < 0) {        fork_error_function ();} else if (ppid == 1) {        child_function ();} else {        parent_function ();}

注意fork在两个完全独立的进程返回。每个进程都有自己的地址空间,有它们自己的变量(除了一些特别的SysV IPC变量共用,但这是很特殊的情况)。
这种独立性,提供了内存保护也更稳定,但是这导致的问题是,当你想让多个进程处理同一个任务/问题的时候。是的,你可以使用pipes或者SysV IPC在进程间通信,但是还是有一些其它的问题。

  • 在进程间切换的代价很高
  • 进程调度器可以处理的进程数目有严重的限制
  • 用于进程间同步的变量很慢

由于这些原因,或者其它某些原因,线程(轻量的进程)就非常有用了。线程之间分享相同的地址空间,它们是在进程内部调度,因此避免了进程间的低效问题。

一个非常受欢迎的创建多线程的API是pthreads,又以POSIX threads著称P1003.1c, or ISO/IEC 9945-1:1990c.这个API就是今天教程的主题。


并发性

并发的好处

在许多情况下多线程可以非常简单的写出优雅并且高效的代码。下面的这些种类的问题非常适合使用多线程。

阻塞IO:一个程序要处理一系列的IO操作有下面三种选择。串行的处理IO请求,一个完成了才能进行下一个。也可以使用异步IO,处理所有复杂的异步信号,polling或者selects。或者使用同步IO,每一个IO请求都单独创建一个线程来处理。在这种情况下,多线程可以明显的提高性能降低代码复杂度。

多处理器:如果你使用的线程库支持多处理器,你可以通过在每一个处理器上运行线程来显著的提高性能。这在你的程序需要大量计算的时候非常有用。

用户界面:将耗时的操作放在线程中来执行,这样UI界面可以继续和用户交互。

服务:服务于多个客户端的服务器可以通过适当使用并发作出反应更灵敏。这在传统上一直通过使用fork()系统调用来实现。然而,在某些情况下,尤其是对于大型高速缓存打交道时,线程可以帮助提高内存的利用率。甚至代替fork()处理并发操作,在某些fork()不适用的情况下。

但是这里也有一些问题,因为多个线程分享相同的地址空间。最大的问题就是数据竞争。考虑一下下面这个代码:

THREAD 1                THREAD 2a = data;               b = data;a++;                    b--;data = a;               data = b;

如果代码是串行的执行(先执行THREAD 1,然后执行THREAD 2),那么这里没有问题。但是线程执行的顺序是完全随机的,所以看一下下面这种情况:

THREAD 1                THREAD 2a = data;                        b = data;a++;                        b--;data = a;                        data = b;[data = data - 1!!!!!!!]

数据最后可能是+1,0,-1,这里没有办法知道是哪一个结果,因为这是完全不确定的。

解决办法是提供一种功能,当数据正在被一个线程访问时,其它线程则阻塞。pthreads使用mutex来实现这个功能。


pthreads库的使用

创建一个POSIX线程

使用pthread_create()创建线程。

#include <pthread.h>int pthread_create (pthread_t *thread_id, const pthread_attr_t *attributes,                void *(*thread_function)(void *), void *arguments);

这个函数创建一个新的线程。pthread_t用来标记这个新的线程。attributes用来定义线程的一些属性,如果传NULL则表示使用默认的属性。thread_function代表这个线程要执行的函数,如果这个函数结束了,那么线程也结束了。arguments是传递给线程函数thread_function的参数。

线程在thread_function函数执行完毕时推出,或者明确的调用pthread_exit()来退出线程。

int pthread_exit (void *status);

status是线程的返回值。(注意线程函数返回void *),所以调用return (void *)和这个函数功能一样。

一个线程可以使用pthread_join()等待另一个线程退出

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

线程退出的状态会放在status_ptr中。

一个线程可以调用pthread_self获取自己的线程id。

pthread_t pthread_self ();

可以使用pthread_equal()来比较线程id是否相同。

int pthread_equal(pthread_t t1, pthread_t t2);

返回0表示是不同的线程,其他情况返回非0;

锁有两个基本操作,lock和unlock。如果锁一个锁是unlock的此时线程调用lock,那么这个锁被锁住线程继续运行。如果这个锁是locked,那么这个线程将会阻塞直到拥有这个锁的人释放锁。

这里有5个基本的处理锁的方法。
初始化一个锁,第一个参数锁的指针,第二个参数传NULL使用默认的属性。

int pthread_mutex_init (pthread_mutex_t *mut, const pthread_mutexattr_t *attr);

对mutex加锁:

int pthread_mutex_lock (pthread_mutex_t *mut);

对mutex解锁:

int pthread_mutex_unlock (pthread_mutex_t *mut);

尝试加锁,尝试获得一个锁,如果成功则加锁,否则返回EBUSY。

int pthread_mutex_trylock (pthread_mutex_t *mut);

销毁一个锁,释放锁的内存和跟锁相关的一些其它资源。

int pthread_mutex_destroy (pthread_mutex_t *mut);

一个简单的例子:
考虑先前那个例子。

THREAD 1                        THREAD 2pthread_mutex_lock (&mut);                                      pthread_mutex_lock (&mut); a = data;                       /* blocked */a++;                            /* blocked */data = a;                       /* blocked */pthread_mutex_unlock (&mut);    /* blocked */                                b = data;                                b--;                                data = b;                                pthread_mutex_unlock (&mut);[数据的竞争被消除了]

条件变量

锁可以让你避免数据竞争,在你操作共享数据的时候保护你,但是它不能让你在某个数据上等待。
条件变量可以解决这个问题。

你可以在条件变量上执行下面6中操作:

初始化,attr传空使用默认的属性。

int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *attr);

等待某个条件成立,这个函数总是阻塞的。

int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mut);

该函数内部的伪代码如下:

pthread_cond_wait (cond, mut)begin        pthread_mutex_unlock (mut);        block_on_cond (cond);        pthread_mutex_lock (mut);end

注意该函数在阻塞钱会是否锁,而在返回时会去获得锁。在重新获取锁时会被会有一段时间,所以这个函数返回时,需要重新检查条件变量的值是否成立。

发送一个信号,唤醒某个在这个条件变量上等待的线程。

int pthread_cond_signal (pthread_cond_t *cond);

广播唤醒,唤醒所有在等待这个条件的线程:

int pthread_cond_broadcast (pthread_cond_t *cond);

带超时的等待某个条件:

int pthread_cond_timedwait (pthread_cond_t *cond, pthread_mutex_t *mut,  const struct timespec *abstime);

超时时间是绝对时间,如果时间带了,这个条件还没有等到,将返回ETIMEDOUT

struct timespec to {        time_t tv_sec;        long tv_nsec;};

销毁,销毁这个条件变量

int pthread_cond_destroy (pthread_cond_t *cond);
1 0