线程

是计算机中独立运行的最小单位,运行时占用很少的系统资源。可以把线程看成是操作系统分配CPU时间的基本单元。一个进程可以拥有一个至多个线程。它线程在进程内部共享地址空间、打开的文件描述符等资源。同时线程也有其私有的数据信息,包括:线程号、寄存器(程序计数器和堆栈指针)、堆栈、信号掩码、优先级、线程私有存储空间。

为什么有了进程的概念后,还要再引入线程呢?使用多线程到底有哪些好处?什么的系统应该选用多线程?我们首先必须回答这些问题。
  使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

  使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
  1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
  3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

创建线程

1 #include <pthread.h>
2 int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
 pthread_t pthread_self(void);
 int pthread_equal(pthread_t thread1,pthread_t thread2);
 int pthread_once(pthread_once_t *once_control,void(*init_routine)(void));

linux系统支持POSIX多线程接口,称为pthread。编写linux下的多线程程序,需要包含头文件pthread.h,链接时需要使用库libpthread.a。

如果在主线程里面创建线程,程序就会在创建线程的地方产生分支,变成两个部分执行。线程的创建通过函数pthread_create来完成。成功返回0

参数:

thread: 参数是一个指针,当线程成功创建时,返回创建线程ID。

attr: 用于指定线程的属性

start_routine: 该参数是一个函数指针,指向线程创建后要调用的函数。

arg: 传递给线程函数的参数。

一个简单的创建线程程序:

$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> int * thread(void *arg)
{
printf("thread id is %d.\n",pthread_self());
return NULL;
} int main()
{
pthread_t id;
printf("Main thread id is %d \n",pthread_self());
if(!pthread_create(&id,NULL,(void *)thread,NULL))
{
printf("succeed!\n");
return 0;
}
else
{printf("Fail to Create Thread");
return -1;
}
}
$ ./main
Main thread id is 1
succeed!
thread id is 2.
 

线程终止

两种方式终止线程。

第一通过return从线程函数返回,

第二种通过调用pthread_exit()函数使线程退出。

需要注意的地方:一是,主线程中如果从main函数返回或是调用了exit函数退出主线程,则整个进程终止,此时所有的其他线程也将终止。另一种是,如果主线程调用pthread_exit函数,则仅仅是主线程消亡,进程不会结束,其他线程也不会结束,知道所有的线程都结束时,进程才结束。

线程属性

1 /* man pthread_attr_init */
2 typedef struct
3 {
4   int                    detachstate;    //是否与其他线程脱离同步
5   int                    schedpolicy;    //新线程的调度策略
6   struct sched_param    schedparam;        //运行优先级等
7   int                    inheritsched;    //是否继承调用者线程的值
8   int                    scope;            //线程竞争CPU的范围(优先级的范围)
9   size_t                guardsize;        //警戒堆栈的大小
10   int                    stackaddr_set;    //堆栈地址集
11   void *                stackaddr;        //堆栈地址
12   size_t                stacksize;        //堆栈大小
13 } pthread_attr_t;
属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。

关于线程的绑定,牵涉到另外一个概念:轻进程(LWP:Light Weight Process)。轻进程可以理解为内核线程,它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的,一个轻进程可以控制一个或多个线程。默认状况下,启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的,这种状况即称为非绑定的。绑定状况下,则顾名思义,即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度,这是因为CPU时间片的调度是面向轻进程的,绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。
  设置线程绑定状态的函数为pthread_attr_setscope,它有两个参数,第一个是指向属性结构的指针,第二个是绑定类型,它有两个取值:PTHREAD_SCOPE_SYSTEM(绑定的)和PTHREAD_SCOPE_PROCESS(非绑定的)。下面的代码即创建了一个绑定的线程。
#include <pthread.h>
pthread_attr_t attr;
pthread_t tid;

/*初始化属性值,均设为默认值*/
pthread_attr_init(&attr);
pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

pthread_create(&tid, &attr, (void *) my_function, NULL);

  线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
  另外一个可能常用的属性是线程的优先级,它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放,一般说来,我们总是先取优先级,对取得的值修改后再存放回去。

线程等待——正确处理线程终止

1 #include <pthread.h>
2 void pthread_exit(void *retval);
3 void pthread_join(pthread_t th,void *thread_return);       //挂起等待th结束,*thread_return=retval;
4 int pthread_detach(pthread_t th);

线程只能被一个线程等待终止(第一个能正常返回),并且应处于join状态(非DETACHED)。

在 Linux 平台下,当处理线程结束时需要注意的一个问题就是如何让一个线程善始善终,让其所占资源得到正确释放。在 Linux 平台默认情况下,虽然各个线程之间是相互独立的,一个线程的终止不会去通知或影响其他的线程。但是已经终止的线程的资源并不会随着线程的终止而得到释放,我们需要调用 pthread_join() 来获得另一个线程的终止状态并且释放该线程所占的资源。

调用该函数的线程将挂起,等待 th 所表示的线程的结束。 thread_return 是指向线程 th 返回值的指针。需要注意的是 th 所表示的线程必须是 joinable 的,即处于非 detached(游离)状态;并且只可以有唯一的一个线程对 th 调用 pthread_join() 。如果 th 处于 detached 状态,那么对 th 的 pthread_join() 调用将返回错误。

如果你压根儿不关心一个线程的结束状态,那么也可以将一个线程设置为 detached 状态,从而来让操作系统在该线程结束时来回收它所占的资源。将一个线程设置为 detached 状态可以通过两种方式来实现。一种是调用 pthread_detach() 函数,可以将线程 th 设置为 detached 状态。其申明如清单 10 。

另一种方法是在创建线程时就将它设置为 detached 状态,首先初始化一个线程属性变量,然后将其设置为 detached 状态,最后将它作为参数传入线程创建函数 pthread_create(),这样所创建出来的线程就直接处于 detached 状态。方法如清单 11 。

     创建 detach 线程:

pthread_t       tid; 
    pthread_attr_t  attr; 
    pthread_attr_init(&attr); 
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); 
    pthread_create(&tid, &attr, THREAD_FUNCTION, arg);

总之为了在使用 Pthread 时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调用 pthread_join() 函数来对其进行资源回收。

线程私有数据

进程内的所有线程共享进程的数据空间,因此全局变量为所有线程所共有。但有时线程也需要保存自己的私有数据,这时可以创建线程私有数据(Thread-specific Date)TSD来解决。在线程内部,私有数据可以被各个函数访问,但对其他线程是屏蔽的。例如我们常见的变量errno,它返回标准的出错信息。它显然不能是一个局部变量,几乎每个函数都应该可以调用它;但它又不能是一个全局变量,否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量,我们就必须使用线程数据。我们为每个线程数据创建一个键,它和这个键相关联,在各个线程里,都使用这个键来指代线程数据,但在不同的线程里,这个键代表的数据是不同的,在同一个线程里,它代表同样的数据内容。

线程私有数据采用了一键多值的技术,即一个键对应多个数值,访问数据时好像是对同一个变量进行访问,但其实是在访问不同的数据。

创建私有数据的函数有4个:pthread_key_create(创建), pthread_setspecific(设置), pthread_getspecific(获取), pthread_key_delete(删除)。

1 #include <pthread.h>
2 int pthread_key_creadte(pthread_key_t *key,void (*destr_fuction) (void *));
3 int pthread_setspecific(pthread_key_t key,const void * pointer));
4 void * pthread_getspecific(pthread_key_t key);
5 int pthread_key_delete(ptherad_key_t key);

线程同步

线程的最大特点是资源的共享性,但资源共享中的同步问题是多线程编程的难点。linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和异步信号。

1)互斥锁(mutex)

通过锁机制实现线程间的同步。同一时刻只允许一个线程执行一个关键部分的代码。

1 int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr);
2 int pthread_mutex_lock(pthread_mutex *mutex);
3 int pthread_mutex_destroy(pthread_mutex *mutex);
4 int pthread_mutex_unlock(pthread_mutex *
      (1)先初始化锁init()或静态赋值pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIER
      attr_t有:
      PTHREAD_MUTEX_TIMED_NP:其余线程等待队列
      PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁,允许线程多次加锁,不同线程,解锁后重新竞争
      PTHREAD_MUTEX_ERRORCHECK_NP:检错,与一同,线程请求已用锁,返回EDEADLK;
      PTHREAD_MUTEX_ADAPTIVE_NP:适应锁,解锁后重新竞争
      (2)加锁,lock,trylock,lock阻塞等待锁,trylock立即返回EBUSY
      (3)解锁,unlock需满足是加锁状态,且由加锁线程解锁
      (4)清除锁,destroy(此时锁必需unlock,否则返回EBUSY,//Linux下互斥锁不占用资源内存
$ cat main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h> pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
int a; int * thread(void *arg)
{
printf("thread id is %d.\n",pthread_self());
pthread_mutex_lock(&mutex);
a=10;
printf("a changed to %d.\n",a);
pthread_mutex_unlock(&mutex);
return NULL;
} int main()
{
pthread_t id;
printf("Main thread id is %d \n",pthread_self());
a=3;
printf("In main func a=%d\n",a); if(!pthread_create(&id,NULL,(void *)thread,NULL))
{
{
printf("Create thread succeed!\n");
}
else
{printf("Fail to Create Thread");
return -1;
}
pthread_join(&id,NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
-bash-3.00$ ./main
Main thread id is 1
In main func a=3
Create thread succeed!
-bash-3.00$

2)条件变量(cond)

利用线程间共享的全局变量进行同步的一种机制。


1 int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);     
2 int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
3 int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
4 int pthread_cond_destroy(pthread_cond_t *cond);  
5 int pthread_cond_signal(pthread_cond_t *cond);
6 int pthread_cond_broadcast(pthread_cond_t *cond);  //解除所有线程的阻塞
      (1)初始化.init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER;属性置为NULL
      (2)等待条件成立.pthread_wait,pthread_timewait.wait()释放锁,并阻塞等待条件变量为真
      timewait()设置等待时间,仍未signal,返回ETIMEOUT(加锁保证只有一个线程wait)
      (3)激活条件变量:pthread_cond_signal,pthread_cond_broadcast(激活所有等待线程)
      (4)清除条件变量:destroy;无线程等待,否则返回EBUSY

对于int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond
pthread_mutex_t *mutex, const struct timespec *abstime);
一定要在mutex的锁定区域内使用。

如果要正确的使用pthread_mutex_lock与pthread_mutex_unlock,请参考
pthread_cleanup_push和pthread_cleanup_pop宏,它能够在线程被cancel的时候正确的释放mutex!

另外,posix1标准说,pthread_cond_signal与pthread_cond_broadcast无需考虑调用线程是否是mutex的拥有者,也就是所,可以在lock与unlock以外的区域调用。如果我们对调用行为不关心,那么请在lock区域之外调用吧。

1 #include <stdio.h>
2 #include <pthread.h>

4 pthread_mutex_t mutex;
5 pthread_cond_t cond;
6 void *thread1(void *arg)
7 {
8     pthread_cleanup_push(pthread_mutex_unlock,&mutex);
9     //提供函数回调保护
10     while(1){
11         printf("thread1 is running\n");
12         pthread_mutex_lock(&mutex);
13         pthread_cond_wait(&cond,&mutex);
14         printf("thread1 applied the condition\n");
15         pthread_mutex_unlock(&mutex);
16         sleep(4);
17     }
18     pthread_cleanup_pop(0);
19 }
20 
21 void *thread2(void *arg)
22 {
23     while(1){
24         printf("thread2 is running\n");
25         pthread_mutex_lock(&mutex);
26         pthread_cond_wait(&cond,&mutex);
27         printf("thread2 applied the condition\n");
28         pthread_mutex_unlock(&mutex);
29         sleep(1);
30     }
31 }
32 int main()
33 {
34     pthread_t thid1,thid2;
35     printf("condition variable study!\n");
36     pthread_mutex_init(&mutex,NULL);
37     pthread_cond_init(&cond,NULL);
38     pthread_create(&thid1,NULL,(void*)thread1,NULL);
39     pthread_create(&thid2,NULL,(void*)thread2,NULL);
40     do{
41         pthread_cond_signal(&cond);
42     }while(1);
43     sleep(20);
44     pthread_exit(0);
45     return 0;

3)信号量

如同进程一样,线程也可以通过信号量来实现通信,虽然是轻量级的。

信号量函数的名字都以"sem_"打头。线程使用的基本信号量函数有四个。
#include <semaphore.h>
int sem_init (sem_t *sem , int pshared, unsigned int value);
这是对由sem指定的信号量进行初始化,设置好它的共享选项(linux 只支持为0,即表示它是当前进程的局部信号量),然后给它一个初始值VALUE。

两个原子操作函数:
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
这两个函数都要用一个由sem_init调用初始化的信号量对象的指针做参数。
sem_post:给信号量的值加1;
sem_wait:给信号量减1;对一个值为0的信号量调用sem_wait,这个函数将会等待直到有其它线程使它不再是0为止。

int sem_destroy(sem_t *sem);
这个函数的作用是再我们用完信号量后都它进行清理。归还自己占有的一切资源。

linux 线程详解的更多相关文章

  1. 通用线程:POSIX 线程详解,第 3 部分 条件互斥量(pthread_cond_t)

    使用条件变量提高效率 本文是 POSIX 线程三部曲系列的最后一部分,Daniel 将详细讨论如何使用条件变量.条件变量是 POSIX 线程结构,可以让您在遇到某些条件时“唤醒”线程.可以将它们看作是 ...

  2. Docker 基础技术之 Linux cgroups 详解

    PS:欢迎大家关注我的公众号:aCloudDeveloper,专注技术分享,努力打造干货分享平台,二维码在文末可以扫,谢谢大家. 推荐大家到公众号阅读,那里阅读体验更好,也沉淀了很多篇干货. 前面两篇 ...

  3. (转)Linux PS 详解

    原文:https://cn.aliyun.com/jiaocheng/162702.html 摘要:原文地址:http://www.cnblogs.com/wangkangluo1/archive/2 ...

  4. 通用线程:POSIX 线程详解,第 3 部分

    通用线程:POSIX 线程详解,第 3 部分 使用条件变量提高效率 Daniel Robbins, 总裁兼 CEO, Gentoo Technologies, Inc. 简介: 本文是 POSIX 线 ...

  5. POSIX 线程详解(经典必看)

    http://www.cnblogs.com/sunminmin/p/4479952.html 总共三部分: 第一部分:POSIX 线程详解                               ...

  6. Linux Ptrace 详解

    转 https://blog.csdn.net/u012417380/article/details/60470075 Linux Ptrace 详解 2017年03月05日 18:59:58 阅读数 ...

  7. Linux命令详解之—tail命令

    tail命令也是一个非常常用的文件查看类的命令,今天就为大家介绍下Linux tail命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux tail命令主要用来从指定点开始将文 ...

  8. Linux命令详解之—less命令

    Linux下还有一个与more命令非常类似的命令--less命令,相比于more命令,less命令更加灵活强大一些,今天就给大家介绍下Linux下的less命令. 更多Linux命令详情请看:Linu ...

  9. Linux命令详解之—more命令

    Linux more命令同cat命令一样,多用来查看文件内容,本文就为大家介绍下Linux more命令的用法. 更多Linux命令详情请看:Linux命令速查手册 Linux的more命令类似 ca ...

随机推荐

  1. 启动Weblogic问题集锦

    报错1:Could not obtain the localhost address. The most likely cause is an error in the network configu ...

  2. kubernets event 分析

    1. event 是一个很重要的组成部分 event 分析 Kubernetes(K8s)Events介绍(上) Kubernetes Events介绍(中) Kubernetes Events介绍( ...

  3. mysql5.7.21下载及安装步骤

    以前都是用的5.6的数据库,现在想着换个新版本数据库.跟上时代的步伐,于是安装了一天才安装好.具体步骤如下: 1.官网下载mysql解压zip版,由于客户端安装版都是32位的,我的电脑是64位系统,所 ...

  4. Windows环境下利用anaconda3安装python版本的Xgboost

    网上有各种不同安装Xgboost的教程,但是有些教程对于一个新手来说,照着做安装成功是很困难的.本人也是新手,第一次安装Xgboost的时候,照着某个教程做,结果总是安装不上,甚至想到要放弃.后来经一 ...

  5. IDEA调用其它模块module的类方法

    IDEA支持调用本project中其他模块的包里面的方法(需要配置该模块和src同级的.iml文件,配置完需要等一会才生效,尝试切换到桌面以...) 这样会使IDEA的project的模块间有依赖,该 ...

  6. 前端——Bootstrap

    Bootstrap介绍 Bootstrap是Twitter开源的基于HTML.CSS.JavaScript的前端框架. 它是为实现快速开发Web应用程序而设计的一套前端工具包. 以前自己写的html的 ...

  7. 编码原则 之 Once and Only Once

    原文 The Once and Only Once principle can be thought of as a subset of the Don’t Repeat Yourself princ ...

  8. 多邻国学英语 tips

    来源: https://www.cnblogs.com/daysme整理了一分多邻国学英语中的相关语法文档. 地方 null 现在完成时 null 反身代词 浓缩的精华:反身代词就是 “XX 自己” ...

  9. Python打开新世界的大门-入门篇1

    目录 题记 Python技巧.避坑及心得 八种数据类型 循环 函数 Homework 题外话 之前没有写博客的习惯,现在开始写觉得入门也太晚了吧,看看同龄的大哥都写了十几万字.于是 ...

  10. 16_Linux网络配置

    A类:255.0.0.0        8 0 000 0001 - 0 111 1111 127用户回环,1-126 2^7-1个A类地址 容纳多少个主机:2^24-2 主机位全0:网络地址 主机位 ...