linux 线程编程详解

1.线程的概念:


线程和进程有一定的相似性,通常称为轻量级的进程

同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程都有自身控制流

(它自己的指令计数器和cpu时钟)和各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。 一个进程可以有很多线程,每条线程并行执行不同的任务。

线程可以提高应用程序在多核环境下处理诸如文件I/O或者socket I/O等会产生堵塞的情况的表现性能。在Unix系统中,一个进程包含很多东西,包括可执行程序以及一大堆的诸如文件描述符地址空间等资源。在很多情况下,完成相关任务的不同代码间需要交换数据。如果采用多进程的方式,那么通信就需要在用户空间和内核空间进行频繁的切换,开销很大。但是如果使用多线程的方式,因为可以使用共享的全局变量,所以线程间的通信(数据交换)变得非常高效

下图很明白的表示了进程和线程在资源和控制流的差别

按照教科书上的定义,进程是资源管理的最小单位,线程是程序执行的最小单位。在操作系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减小(进程/线程)上下文切换开销。

无论按照怎样的分法,一个进程至少需要一个线程作为它的指令执行体,进程管理着资源(比如cpu、内存、文件等等),而将线程分配到某个cpu上执行。一个进程当然可以拥有多个线程,此时,如果进程运行在SMP机器上,它就可以同时使用多个cpu来执行各个线程,达到最大程度的并行,以提高效率;同时,即使是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型一样,使设计更简洁、功能更完备,程序的执行效率也更高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也可以用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多了,从语义上来说,同时响应多个输入这样的功能,实际上就是共享了除cpu以外的所有资源的。

线程的函数调用一般成功返回0,失败返回非0

2.线程的操作


主要的三个问题:

线程的创建

线程的终止

线程的同步互斥

(1)线程的创建

若是你的linux没有线程相关的man文档,安装在线man帮助:

sudo apt-get install manpages-posix manpages-posix-dev
#include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);

//四个参数意义
pthread_t *thread:  用来得到所创建的线程的ID,所以要创建变量,传入地址。
pthread_attr_t *attr: 线程的属性结构体变量地址,如果不特别指定属性,可以使用默认属性NULL。
start_routine: 线程函数,线程函数一般一个不退出的死循环。
arg:这是Linux系统在调用线程函数时传入的参数。

Compile and link with -pthread.

(2).线程的终止

linux man 文档原文,原文文档说的很明白

The new thread terminates(终止) in one of the following ways:

       * It  calls  pthread_exit(3),  specifying(指定)  an exit status value that is
         available  to  another  thread  in  the  same  process   that   calls
         pthread_join(3).

       * It  returns  from  start_routine().   This  is  equivalent(等效) to calling
         pthread_exit(3) with the value supplied in the return statement.

       * It is canceled (see pthread_cancel(3)).

       * Any of the threads in the process calls exit(3), or the  main  thread
         performs  a  return  from main().  This causes the termination of all
         threads in the process.

如果需要只终止某个线程而不终止整个进程,可以有三种方法:

从线程函数 return 。这种方法对主线程不适用,从 main 函数 return 相当于调用 exit 。

一个线程可以调用 pthread_cancel 终止同一进程中的另一个线程。

线程可以调用 pthread_exit 终止自己。

(3).线程的属性(创建进程的第二个参数)

The attr argument points to a pthread_attr_t structure  whose  contents

are  used  at  thread creation time to determine attributes for the new

thread; this structure is initialized  using  pthread_attr_init(3)  and

related  functions.   If  attr is NULL, then the thread is created with

default attributes.

设置线程的分离属性

(1).pthread_attr_t attr;

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

(2).pthread_detach(pthread_self())

(4)线程的互斥锁

由于线程共享进程的资源和地址空间,在对这些资源进行操作时,就必须要考虑到线程间资源访问的同步与互斥问题

有两种机制:互斥锁和信号量,互斥锁适用于可用资源是唯一的情况,信号量适用于可用资源为多个的情况

互斥锁的基本函数:
1、pthread_mutex_init():互斥锁初始化
2、pthread_mutex_lock():互斥锁上锁
3、pthread_mutex_trylock():互斥锁判断上锁

pthread_mutex_trylock()调用在参数mutex指定的mutex对象当前被锁住的时候立即返回,除此之外,pthread_mutex_trylock()跟pthread_mutex_lock()功能完全一样。
4、pthread_mutex_unlock():互斥锁解锁
5、pthread_mutex_destory():消除互斥锁

  • 在主线程中初始化锁为解锁状态

    • pthread_mutex_t mutex;
    • pthread_mutex_init(&mutex, NULL);
  • 在编译时初始化锁为解锁状态
    • 锁初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 访问对象时的加锁操作与解锁操作
    • 加锁 pthread_mutex_lock(&mutex)
    • 释放锁 pthread_mutex_unlock(&mutex)
  • 锁保护的并不是我们的共享变量(或者说是共享内存),对于共享的内存而言,用户是无法直接对其保护的,因为那是物理内存,无法阻止其他程序的代码访问。事实上,锁之所以对关键区域进行了保护,在本例中,是因为所有线程都遵循了一个规则,那就是在进入关键区域钱加同一把锁,在退出关键区域钱释放同一把
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

int sharedi = 0;
void increse_num(void);

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int main(){
    int ret;
    pthread_t thrd1, thrd2, thrd3;

    ret = pthread_create(&thrd1, NULL, (void *)increse_num, NULL);
    ret = pthread_create(&thrd2, NULL, (void *)increse_num, NULL);
    ret = pthread_create(&thrd3, NULL, (void *)increse_num, NULL);

    pthread_join(thrd1, NULL);
    pthread_join(thrd2, NULL);
    pthread_join(thrd3, NULL);

    printf("sharedi = %d\n", sharedi);

    return 0;

}

void increse_num(void) {
    long i,tmp;
    for(i=0; i<=100000; i++) {
    /*加锁*/
        if (pthread_mutex_lock(&mutex) != 0) {
           perror("pthread_mutex_lock");
           exit(EXIT_FAILURE);
        }
        tmp = sharedi;
        tmp = tmp + 1;
        sharedi = tmp;
    /*解锁锁*/
        if (pthread_mutex_unlock(&mutex) != 0) {
            perror("pthread_mutex_unlock");
            exit(EXIT_FAILURE);
        }
    }
}

====================

来自《高级unix编程》的一个互斥锁的一个问题

大概是说在线程的调用函数里,当调用解锁函数pthread_mutex_unlock(&mutex)失败了,将会导致线程的退出,而互斥锁将被锁定了。

而解决办法是用函数把解锁和加锁过程写入一个函数进行封装,然后线程调用函数对该封装函数进行调用。

//信号量是转自别人的,链接会在下文给出

(5).线程信号量

锁有一个很明显的缺点,那就是它只有两种状态:锁定与不锁定。

信号量本质上是一个非负数的整数计数器,它也被用来控制对公共资源的访问。当公共资源增加的时候,调用信号量增加函数sem_post()对其进行增加,当公共资源减少的时候,调用函数sem_wait()来减少信号量。其实,我们是可以把锁当作一个0-1信号量的。

它们是在/usr/include/semaphore.h中进行定义的,信号量的数据结构为sem_t, 本质上,它是一个long型整数

相关函数

在使用semaphore之前,我们需要先引入头文件#include <semaphore.h>

  • 初始化信号量: int sem_init(sem_t *sem, int pshared, unsigned int value);

    • 成功返回0,失败返回-1
    • 参数
    • sem:指向信号量结构的一个指针
    • pshared: 不是0的时候,该信号量在进程间共享,否则只能为当前进程的所有线程们共享
    • value:信号量的初始值
  • 信号量减1操作,当sem=0的时候该函数会堵塞 int sem_wait(sem_t *sem);
    • 成功返回0,失败返回-1
    • 参数
    • sem:指向信号量的一个指针
  • 信号量加1操作 int sem_post(sem_t *sem);
    • 参数与返回同上
  • 销毁信号量 int sem_destroy(sem_t *sem);
    • 参数与返回同上
  • /*************************************************************************
        > File Name: sem.c
        > Author: couldtt(fyby)
        > Mail: [email protected]
        > Created Time: 2013年12月15日 星期日 19时25分08秒
     ************************************************************************/
    
    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <semaphore.h>
    
    #define MAXSIZE 10
    
    int stack[MAXSIZE];
    int size = 0;
    sem_t sem;
    
    // 生产者
    void provide_data(void) {
        int i;
        for (i=0; i< MAXSIZE; i++) {
            stack[i] = i;
            sem_post(&sem); //为信号量加1
        }
    }
    
    // 消费者
    void handle_data(void) {
        int i;
        while((i = size++) < MAXSIZE) {
            sem_wait(&sem);
            printf("乘法: %d X %d = %d\n", stack[i], stack[i], stack[i]*stack[i]);
            sleep(1);
        }
    }
    
    int main(void) {
    
        pthread_t provider, handler;
    
        sem_init(&sem, 0, 0); //信号量初始化
        pthread_create(&provider, NULL, (void *)handle_data, NULL);
        pthread_create(&handler, NULL, (void *)provide_data, NULL);
        pthread_join(provider, NULL);
        pthread_join(handler, NULL);
        sem_destroy(&sem); //销毁信号量
    
        return 0;
    }

  • 线程的系统调用

    1.pthread_create

    NAME

    pthread_create - create a new thread

    SYNOPSIS

    #include <pthread.h>

    int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

    void *(*start_routine) (void *), void *arg);

    Compile and link with -pthread.

    2.pthread_exit(3)

    3.pthread_join(3)

    4.pthread_cancel(3)

    5.pthread_attr_init(3)//属性初始化函数

    #include <pthread.h>

    int pthread_attr_init(pthread_attr_t *attr);

    int pthread_attr_destroy(pthread_attr_t *attr);

    Compile and link with -pthread.

    6.pthread_detach()//设置线程分离属性

    7.pthread_self()

参考资料:

【1】http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html

【2】http://www.cnblogs.com/fuyunbiyi/p/3475602.html

【3】《高级unix编程》

linux线程实现机制:https://www.ibm.com/developerworks/cn/linux/kernel/l-thread/

时间: 2024-10-13 11:22:57

linux 线程编程详解的相关文章

Linux串口编程详解

串口本身,标准和硬件 ? 串口是计算机上的串行通讯的物理接口.计算机历史上,串口曾经被广泛用于连接计算机和终端设备和各种外部设备.虽然以太网接口和USB接口也是以一个串行流进行数据传送的,但是串口连接通常特指那些与RS-232标准兼容的硬件或者调制解调器的接口.虽然现在在很多个人计算机上,原来用以连接外部设备的串口已经广泛的被USB和Firewire替代:而原来用以连接网络的串口则被以太网替代,还有用以连接终端的串口设备则已经被MDA或者VGA取而代之.但是,一方面因为串口本身造价便宜技术成熟,

Linux多线程编程详解 [By: HarryAlex]

本文内容主要参考于<Linux程序设计·第3版>.<Linux环境C程序设计>.<C语言核心技术>.<深入理解计算机系统·第2版>,代码运行环境: Linux version 3.10.0-123.el7.x86_64 ([email protected]) (gcc version 4.8.2 20140120 (Red Hat 4.8.2-16) (GCC) ) #1 SMP Thu Jun 4 17:17:49 CST 2015 1. Linux进程与

Linux 网络编程详解十

select int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 参数nfds:readfds集合内文件描述符的最大值(文件描述符的值)加1 参数readfds:用户关心的读文件描述符集合 返回值:如果成功,返回所有sets(所有即readfds,writefds,exceptfds)中描述符的个数,如果超时,返回0,如果出错,返回-1 fd_set

Linux 网络编程详解一

IPv4套接字地址结构 struct sockaddr_in { uint8_t sinlen;(4个字节) sa_family_t sin_family;(4个字节) in_port_t sin_port;(2个字节) struct in_addr sin_addr;(4个字节) char sin_zero[8]; }; sin_len:整个sockaddr_in结构体的长度,部分Linux内核版本没有该成员 sin_family:指定该地址家族,一般设置为AF_INET(使用TCP,UDP协

Linux 网络编程详解九

TCP/IP协议中SIGPIPE信号产生原因 1.假设客户端socket套接字close(),会给服务器发送字节段FIN: 2.服务器接收到FIN,但是没有调用close(),因为socket有缓存区,所以服务器仍然可以向客户端发送数据. 3.如果这种状态下服务器向客户端发送数据,将会引起TCP/IP协议进行RST段重置,导致服务器向当前进程发送SIGPIPE信号, SIGPIPE信号的默认动作是关闭当前进程. 所以在网络编程中必须要捕捉SIGPIPE信号,因为不确定哪个端会突然close().

Linux 网络编程详解八

TCP/IP协议三次握手机制 TCP/IP是全双工通道,两端都可以读写,三次握手机制就是验证TCP/IP是否是全双工通道 1.客户端调用connect()函数,阻塞客户端进程,客户端向服务器发送数据包SYN,包SYN的值是a,客户端状态置为SYN_SENT,表示向服务器发送验证 2.服务器在调用listen()函数后,将socket套接字变成被动套接字,然后服务器端在accept()函数处阻塞,此时服务器端状态是SYN_RCVD,表示等待客户端发送SYN包.当服务器接收到客户端的SYN的数据包之

Linux的SOCKET编程详解(转)

Linux的SOCKET编程详解 1. 网络中进程之间如何通信 进 程通信的概念最初来源于单机系统.由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进 程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如 UNIX BSD有:管道(pipe).命名管道(named pipe)软中断信号(signal) UNIX system V有:消息(message).共享存储区(shared memory)和信号量(semaphore)等. 他们都仅限于用在本机进程之间通信.网间进

Java多线程编程详解

线程的同步 由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题.Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问. 由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,这套机制就是 synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法:通过在方法声明中加入 synch

红帽Linux故障定位技术详解与实例(2)

红帽Linux故障定位技术详解与实例(2) 2011-09-28 14:26 圈儿 BEAREYES.COM 我要评论(0) 字号:T | T 在线故障定位就是在故障发生时, 故障所处的操作系统环境仍然可以访问,故障处理人员可通过console, ssh等方式登录到操作系统上,在shell上执行各种操作命令或测试程序的方式对故障环境进行观察,分析,测试,以定位出故障发生的原因. AD:2014WOT全球软件技术峰会北京站 课程视频发布 3.内核故障情形及处理 (1)内核panic panic是内