linux多线程编写哲学家,Linux系统编程(三) ------ 多线程编程

一、线程的创建和调度

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

为了进一步减少处理机制的空转时间,支持多处理器及减少上下文切换开销,进程在演化中出现了另一个概念——线程。它是进程内独立的一条运行路线,是处理器调用的最小单元,也可以成为轻量级进程。

进程标识符在内部唯一,只为了进程内的区分。

线程可以对进程的内存空间和资源进程访问,并与同一个进程中的其他线程共享。因此,虽然每个线程同样得开辟一定大小空间拥有自己的栈空间,拥有独立的执行序列,但线程上下文切换的开销比创建进程小得多。

在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。

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

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

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

向线程函数传递参数分为两种:

1)线程函数只有一个参数的情况:直接定义一个变量通过应用传给线程函数。

2)线程函数有多个参数的情况:此时就必须申明一个结构体来包含所有的参数,然后在传入线程函数。

3.线程库

#include ```

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

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

//全局变量,可以是结构体,整型变量,等等。

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)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值