Linux系统编程(三) ------ 多线程编程

一、线程的创建和调度

1.线程是程序执行的某一条指令流的映像。

为了进一步减少处理机制的空转时间,支持多处理器及减少上下文切换开销,进程在演化中出现了另一个概念——线程。它是进程内独立的一条运行路线,是处理器调用的最小单元,也可以成为轻量级进程。
进程标识符在内部唯一,只为了进程内的区分。
线程可以对进程的内存空间和资源进程访问,并与同一个进程中的其他线程共享。因此,虽然每个线程同样得开辟一定大小空间拥有自己的栈空间,拥有独立的执行序列,但线程上下文切换的开销比创建进程小得多。
在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。

线程和进程在使用上各有优缺点:

线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

2.线程函数,用于提供线程执行的指令(代码)。

向线程函数传递参数分为两种:
1)线程函数只有一个参数的情况:直接定义一个变量通过应用传给线程函数。
2)线程函数有多个参数的情况:此时就必须申明一个结构体来包含所有的参数,然后在传入线程函数。

3.线程库

#include <pthread.h>```
Linux系统下的多线程遵循POSIX线程接口,称为pthread。
pthread_t 在头文件/usr/include/bits/pthreadtypes.h中定义:

    typedef unsigned long int pthread_t;
它是一个线程的标识符。在编译命令末注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。

####4.使用pthread_create()函数创建线程:
>函数原型
```c
int pthread_create(pthread_t *tid, const pthread_attr_t *tattr, void *(*start_routine)(void *), void *arg);```

*函数参数及说明:*
- 第一个参数为指向线程标识符的指针。返回成功时,由tid指向的内存单元被设置为新创建线程的线程ID。
- 第二个参数用来设置各种不同的线程属性。
- 第三个参数是线程运行函数的起始地址。新创建的线程从start_routine函数的地址开始运行,该函数只有一个万能指针参数arg,如果需要向start_routine函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg的参数传入。
- 最后一个参数是运行函数的参数。arg指针是最初唯一对指针所指向的对象进行存取的方法,仅当第二个指针基于第一个时,才能对对象进行存取。对对象的存取都限定于基于由arg指针表达式中。 由arg指针主要用于函数形参,或指向由 malloc() 分配的内存空间。arg 数据类型不改变程序的语义。编译器能通过作出arg指针是存取对象的唯一方法的假设,更好地优化某些类型的进程。

*函数返回值:*函数成功返回0。任何其他返回值都表示错误。

####5.使用pthread_join()函数以阻塞的方式等待thread指定的线程结束。
```c
 int pthread_join(pthread_t thread, void **retval);

函数参数及说明:

  • 第一个参数为被等待的线程标识符,标识唯一线程。
  • 第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。

这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。如果线程已经结束,那么该函数会立即返回。
如果没有pthread_join()主线程会很快结束从而使整个进程结束,从而使创建的线程没有机会开始执行就结束了。加入pthread_join()后,主线程会一直等待直到等待的线程结束自己才结束,使创建的线程有机会执行。

函数返回值:函数执行成功返回0。任何其他返回值都表示错误。

提示:

在多线程编程的时候我们往往都是以for循环的形式调用,实际上主线程在第1个线程处就挂起了,在等待1号线程结束后再等待2号线程。当然会出现3,4,5比1,2先结束的情况。主线程还是在等待1,2结束后,发现3,4,5其实早已经结束了,就会回收3,4,5的资源,然后主线程再退出。

6.使用pthread_exit()函数终止并线程

void pthread_exit(void *retval);```
*函数说明及返回值:*
是线程的主动行为;由于一个进程中的多个线程是共享数据段的,因此通常在线程退出之后,退出线程所占用的资源并不会随着线程的终止而得到释放,但是可以用pthread_join()函数来配合同步并释放资源。pthread_exit()终止调用它的线程并返回一个指向某个对象的指针,该返回值可以通过pthread_join函数的第二个参数得到。
唯一的参数是函数的返回代码,只要pthread_join中的第二个参数不是NULL,这个值将被传递给retval。要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用pthread_join的线程则返回错误代码。

####7.使用pthread_self()函数获取当前线程的标识符:
```c
pthread_t pthread_self(void);```
返回获得当前线程自身的标示符ID。pthread_t的类型为unsigned long int,所以在打印的时候要使用%ld方式,否则将产生奇怪的结果。

####8.使用pthread_equal()函数比较判断是否是同一个线程:
```c
int pthread_equal(pthread_t tid1, pthread_t tid2);```
如果tid1和tid2相同,函数返回一个非0值,否则返回0。
如果tid1或tid2中任何一个是非法值,则返回将是不可预料的。

####9.使用pthread_cancel()取消指定线程:
```c
int pthread_cancel(pthread_t thread);```
*函数返回值:*函数成功返回0。任何其他返回值都表示错误。
退出一个线程。
如何响应退出请求取决于目标线程的状态。
当然,线程也不是被动的被别人结束。它可以通过设置自身的属性来决定如何结束。
线程的被动结束分为两种,一种是异步终结,另外一种是同步终结。异步终结就是当其他线程调用 pthread_cancel的时候,线程就立刻被结束。而同步终结则不会立刻终结,它会继续运行,直到到达下一个结束点(cancellation point)。当一个线程被按照默认的创建方式创建,那么它的属性是同步终结。
发送终止信号给thread线程,如果成功则返回0,否则为非0值。发送成功并不意味着thread会终止。

##二、线程并发要求 
1.同步,代码依赖资源有前后关系(竞争),试衣间(互斥)。进程/线程中的部分指令需要按照一定 顺序前后执行。
2.竞争,有竞争才会同步。对于有限资源的共享使用过程中产生的竞争关系。
3.互斥,对于共享资源的操作同时只能有一个进程/线程。
4.死锁,互相等待资源。//不应有
5.饥饿,长时间(设置时间点)无法获取资源。//不应有
6.异步,完全没有关系。不用去保护。进程/线程之间的指令执行无顺序。
[并发性:互斥和同步、死锁和饥饿 ](http://blog.csdn.net/u013271921/article/details/45459351)
[进程同步互斥——不死锁的哲学家问题](http://www.oschina.net/code/snippet_180916_7504)
[操作系统中的互斥,同步与死锁 ](http://blog.csdn.net/t_tbread/article/details/22678549)

##三、线程间通信
信息数据交换,使用多个线程都可见的内存区域。A产生的B使用。标识符

###保护机制:
#####1.线程互斥锁: 
**互斥量(Mutex)**是“mutual exclusion”的缩写。互斥量是实现线程同步,和保护同时写共享数据的主要方法。保障有同一把锁保护的共享资源被多个线程互斥访问。互斥量对共享数据的保护就像一把锁。
######互斥量必须用类型pthread_mutex_t类型声明。
对临界区加锁以实现互斥,当某个进程进入临界区之后,它将锁上临界区,直到它退出临界区为止。
>互斥锁初始化函数
```c
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);```

***函数说明:***
    该函数用于C函数的多线程编程中,互斥锁的初始化。
***函数参数:***
函数是以动态方式创建互斥锁的,第一个参数 mutex 是指向要初始化的互斥锁的指针。第二个参数 attr 是指向新建互斥锁属性对象的指针,该属性对象定义要初始化的互斥锁的属性。如果该指针为 NULL,则使用默认的快速互斥锁属性。
互斥锁的属性在创建锁的时候指定,在LinuxThreads实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。
***函数返回值:***执行成功完成之后会返回0,其他任何返回值都表示出现了错误。
函数成功执行后,互斥锁被初始化为锁住态。
[线程同步:互斥量,死锁](http://blog.csdn.net/tototuzuoquan/article/details/39553761)

>互斥锁的获取(加锁)
```c
 int pthread_mutex_lock(pthread_mutex_t *mutex);```
互斥锁的释放(解锁)
```c
 int pthread_mutex_unlock(pthread_mutex_t *mutex);```
以上两个不可能被两个不同的线程同时得到,而必须等待解锁。
在同一进程中的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。
pthread_mutex_lock()声明开始用互斥锁上锁,此后的代码直至调用pthread_mutex_unlock()为止,均被上锁,即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock()处时,如果该锁此时被另一个线程使用,那此线程被阻塞,即程序将等待到另一个线程释放此互斥锁。

多个线程对资源改写都要互斥(保护)
***提示:***总体来讲, 有几个不成文的基本原则:
- 对共享资源操作前一定要获得锁。
- 完成操作以后一定要释放锁。
- 尽量短时间地占用锁。
- 如果有多锁, 如获得顺序是ABC连环扣, 释放顺序也应该是ABC。
- 线程错误返回时应该释放它所获得的锁。

[pthreads线程(二) 线程同步--互斥量/锁](http://www.cnblogs.com/dongsheng/p/4186358.html)

####2.线程信号量:解决多个线程在使用共享有限资源的同步问题。
线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。

信号量是一个计数器,用于控制访问有限共享资源的线程数。
在操作系统中,信号量是一整数,在sem大于等于零时代表可供并发进程使用的资源实体数,但sem小于零时表示正在等待使用临界区的进程数;
线程信号量,sem_t


>信号量的创建和初始化:
```c
#include <semaphore.h>
int sem_init (sem_t *sem, int pshared, unsigned int value);```
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1。

>信号量的获取,以原子操作的方式将信号量的值--1。
```c
int sem_wait(sem_t * sem);```
sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1。

>信号量的释放,以原子操作的方式将信号量的值++1。
```c
int sem_post(sem_t * sem);```
与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1。

>信号量的销毁,用于对用完的信号量的清理。
```c
int sem_destroy (sem_t *sem);```
成功时返回0,失败时返回-1。

[Linux多线程——使用信号量同步线程 ](http://blog.csdn.net/ljianhui/article/details/10813469/)
[线程同步(互斥锁与信号量的作用与区别)](http://blog.csdn.net/tietao/article/details/7367827)

####3.原子操作
暨如果两个线程企图同时给一个信号量++1或--1,它们之间不会互相干扰。是最安全的操作,用锁锁在一起,或全执行,或全搁置。


#####课程使用各参数(暂时)安排

    创建线程:
    #include <pthread.h>
    //全局变量,可以是结构体,整型变量,等等。
    void *thread_func(void *arg);//声明线程函数
    //主调函数内:
    pthread_t thread_id;    //注册定义线程id
    pthread_create(&thread_id, NULL, thread_func, NULL); //创建线程并指定线程的执行函数,最后一个参数为传到线程函数的参数。
     pthread_join(thread_id, NULL); // 等待指定线程的退出
     //自定义线程函数内:
     pthread_exit(NULL);//返回到join后一个参数。


    线程互斥锁:
            pthread_mutex_t mutex;//全局定义线程互斥锁
            主调函数内:  
            注册定义线程id
            pthread_mutex_init(&mutex, NULL);//初始化线程互斥锁
            创建线程
            等待线程退出
            自定义线程函数内:
            pthread_mutex_lock(&mutex);// 获取互斥锁
            pthread_mutex_unlock(&mutex); // 释放互斥锁
            !!互斥锁的获取和释放必须成对出现!!
    
    信号量:
            sem_t sem;//全局定义信号量
            void sig_handler(int signo);//声明信号处理函数
            主调函数内:  
            注册定义线程id
            signal(SIGINT, sig_handler);//注册信号处理函数
            sem_init(&sem, 0, 0);// 初始化信号量为0
            创建线程
            等待线程退出
            sem_destroy(&sem);// 销毁信号量
            自定义线程函数内:
            while(1)
            sem_wait(&sem);// sem -1 对信号量进行wait(即-1)操作,如果<0,阻塞。
            自定义信号处理函数内:
            sem_post(&sem);//sem +1 对信号量进行post(即+1)操作,如果<=0,系统唤醒一个等待线程。
[进程同步互斥——不死锁的哲学家问题](http://www.oschina.net/code/snippet_180916_7504)
[竞争与同步,互斥量,信号量,死锁,条件变量,哲学家吃饭问题](http://www.th7.cn/system/lin/201509/134476.shtml)
####参考资料
[对Linux中多线程编程中pthread_join的理解](http://www.linuxidc.com/Linux/2013-09/89931.htm)
[Linux 线程操作函数技能总结](http://blog.csdn.net/shaderdx/article/details/50475982)
[pthread多线程编程的学习小结](https://www.oschina.net/question/234345_40365)
[pThreads线程(一) 基本API](http://www.cnblogs.com/dongsheng/p/4184153.html)
[Linux Pthread 深入解析](http://blog.csdn.net/u010009623/article/details/53116814)

推荐阅读更多精彩内容