文章目录
- 一、线程
- 二、创建线程
- pthreand_create()函数
- pthread_attr_setstacksize()函数
- pthread_attr_setdetachstate()函数
- 三、锁
- 阻塞锁
- 非阻塞锁
- 死锁
一、线程
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。
线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,进程的创建所花的时间片要比线程大些,另外进程间的通信比较麻烦,需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效
二、创建线程
一个进程创建后,会首先生成一个缺省的线程,通常称这个线程为主线程(或称控制线程),主线程就是通过main函数进入的线程,由主线程调用pthread_create()创建的线程称为子线程。要注意的是:
1、子线程也可以有自己的入口函数,该函数由用户在创建的时候指定。
2、每个线程都有自己的线程ID,可以通过pthread_self()函数获取。
3、每个进程可创建的最大线程数由具体实现决定。
4、主线程和子线程的默认关系是:无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。
pthreand_create()函数
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void
*arg);
thread:是一个pthread_t类型的指针,他用来返回该线程的线程ID。每个线程都能够通过pthread_self()来获取自己的线程ID(pthread_t类型)。
attr:第二个参数是线程的属性,其类型是pthread_attr_t类型,其定义如下:
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam;// 线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
对于这些属性,我们需要设定的是线程的分离状态。如果有必要也需要修改每个线程的栈大小。每个线程创建后默认是joinable状态,该状态需要主线程调用 pthread_join 等待它退出,否则子线程在结束时,内存资源不能得到释放造成内存泄漏。所以我们创建线程时一般会将线程设置为分离状态。
pthread_attr_setstacksize()函数
#include <pthread.h>
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
pthread_create 创建线程时,若不指定分配堆栈大小,系统会分配默认值。
采用默认值可能会导致内存不足,所以要用pthread_attr_setstacksize()函数来调整设置的堆栈大小。 返回值0,-1分别表示成功与失败。
pthread_attr_setdetachstate()函数
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_attr_setdetachstate()函数主要用来设置线程属性。
主要有以下两种属性:
可会合(joinable):这种关系下,主线程需要明确执行等待操作,在子线程结束后,主线程的等待操作执行完毕,子线程和主线程会合,这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程。在主线程的线程函数内部调用子线程对象的wait函数实现,即使子线程能够在主线程之前执行完毕,进入终止态,也必须执行会合操作,否则,系统永远不会主动销毁线程,分配给该线程的系统资源也永远不会释放。
相分离(detached):表示子线程无需和主线程会合,也就是相分离的,这种情况下,子线程一旦进入终止状态,这种方式常用在线程数较多的情况下,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困难或不可能的,所以在并发子线程较多的情况下,这种方式也会经常使用。
举一个例子来简单来介绍线程
1 #include <stdio.h>
2 #include <string.h>
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <unistd.h>
6 #include <pthread.h>
7
8 void *thread_worker1(void *args);
9 void *thread_worker2(void *args);
10
11 int main(int argc, char **argv)
12 {
13 int share_var = 1000;
14 pthread_t tid;
15 pthread_attr_t thread_attr;
16
17
18 if( pthread_attr_init(&thread_attr) )
19 {
20 printf("pthread_attr_init() failure: %s\n", strerror(errno));
21 return -1;
22 }
23
24 if( pthread_attr_setstacksize(&thread_attr, 120*1024) )
25 {
26 printf("pthread_attr_setstacksize() failure: %s\n", strerror(errno));
27 return -1;
28 }
29
30 if( pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED) )
31 {
32 printf("pthread_attr_setdetachstate() failure: %s\n", strerror(errno));
33 return -1;
34 }
35
36 pthread_create(&tid, &thread_attr, thread_worker1, &share_var);
37 printf("Pthread worker1 tid[%ld] create ok\n", tid);
38
39 pthread_create(&tid, NULL, thread_worker2, &share_var);
40 printf("Pthread worker2 tid[%ld] create ok\n", tid);
41
42 pthread_attr_destroy(&thread_attr);
43
44 pthread_join(tid, NULL);
45
46
47 while(1)
48 {
49 printf("Mian/Control thread share_var: %d\n", share_var);
50 sleep(10);
51 }
52 }
53
54 void *thread_worker1(void *args)
55 {
56 int *ptr = (int *)args;
57
58 if( !args )
59 {
60 printf(" %s() get invalid arguments\n", __FUNCTION__);
61 pthread_exit(NULL);
62 }
63
64 printf("Thread worker 1 [%ld] start running...\n", pthread_self());
65
66 while(1)
67 {
68 printf("+++: %s before share_var++: %d\n", __FUNCTION__, *ptr);
69 *ptr +=1;
70 sleep(2);
71 printf("+++: %s after sleep shared_var: %d\n", __FUNCTION__, *ptr);
72 }
73
74 printf("Thread worker1 exit...\n");
75
76 return NULL;
77 }
78
79 void *thread_worker2(void *args)
80 {
81 int *ptr = (int *)args;
82
83 if( !args )
84 {
85 printf("%s() get invalid arguments\n", __FUNCTION__);
86 pthread_exit(NULL);
87 }
88
89 printf("Thread worker 2 [%ld] start running...\n", pthread_self());
90
91
92 while(1)
93 {
94 printf("---: %s before shared_var++: %d\n", __FUNCTION__, *ptr);
95 *ptr += 1;
96 sleep(2);
97 printf("---: %s after sleep shared_var: %d\n", __FUNCTION__, *ptr);
98 }
99
100 printf("Thread worker2 exit...\n");
101
102 return NULL;
103 }
三、锁
阻塞锁
多个线程同时调用同一个方法的时候,所有线程都被排队处理了。让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态。
非阻塞锁
多个线程同时调用一个方法的时候,当某一个线程最先获取到锁,这时其他线程判断没拿到锁,这时就直接返回,只有当最先获取到锁的线程释放,其他线程才能进来,在它释放之前其它线程都会获取失败。
死锁
如果多个线程要调用多个对象,则在上锁的时候可能会出现“死锁”。举个例子: A、B两个线程会同时使用到两个共享变量m和n,同时每个变量都有自己相应的锁M和N。 这时A线程首先拿到M锁访问m,接下来他需要拿N锁来访问变量n; 而如果此时B线程拿着N锁等待着M锁的话,就造成了线程“死锁”。
死锁产生的4个必要条件
1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
产生死锁需要四个条件,那么,只要这四个条件中至少有一个条件得不到满足,就不可能发生死锁了。由于互斥条件是非共享标题资源所必须的,不仅不能改变,还应加以保证,所以,主要是破坏产生死锁的其他三个条件。