Linux下的C编程实战(四)――“线程”控制与“线程”通信编程

本文介绍Linux环境下线程的创建、终止、同步等控制方法及线程间的通信手段,并通过生产者/消费者问题实例演示线程编程技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux 下的 C 编程实战(四) <?XML:NAMESPACE PREFIX = O />

――“线程”控制与“线程”通信编程

1.Linux “线程”

       笔者曾经在《基于嵌入式操作系统 VxWorks 的多任务并发程序设计》(《软件报》 2006 年第 5~12 期)中详细叙述了进程和线程的区别,并曾经说明 Linux 是一种“多进程单线程”的操作系统。 Linux 本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。大家知道,进程是资源分配的单位,同一进程中的多个线程共享该进程的资源(如作为共享内存的全局变量)。 Linux 中所谓的“线程”只是在被创建的时候“克隆” (clone) 了父进程的资源,因此, clone 出来的进程表现为“线程”,这一点一定要弄清楚。因此, Linux “线程”这个概念只有在打冒号的情况下才是最准确的,可惜的是几乎没有书籍留心去强调这一点。
       Linux 内核只提供了轻量进程的支持,未实现线程模型,但 Linux 尽最大努力优化了进程的调度开销,这在一定程度上弥补无线程的缺陷。 Linux 用一个核心进程(轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成。
目前 Linux 中最流行的线程机制为 LinuxThreads ,所采用的就是线程-进程“一对一”模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。 LinuxThreads Xavier Leroy ([email]Xavier.Leroy@inria.fr[/email]) 负责开发完成,并已绑定在 GLIBC 中发行,它实现了一种 BiCapitalized 面向 Linux Posix 1003.1c pthread ”标准接口。 Linuxthread 可以支持 Intel Alpha MIPS 等平台上的多处理器系统。
按照 POSIX <?XML:NAMESPACE PREFIX = ST1 />1003.1c 标准编写的程序与 Linuxthread 库相链接即可支持 Linux 平台上的多线程,在程序中需包含头文件 pthread. h ,在编译链接时使用命令:
gcc -D -REENTRANT -lpthread xxx. c

其中 -REENTRANT 宏使得相关库函数 ( stdio.h errno.h 中函数 ) 是可重入的、线程安全的 (thread-safe) -lpthread 则意味着链接库目录下的 libpthread.a libpthread.so 文件。使用 Linuxthread 库需要 2.0 以上版本的 Linux 内核及相应版本的 C (libc 5.2.18 libc 5.4.12 libc 6)
2. “线程”控制

线程创建

进程被创建时,系统会为其创建一个主线程,而要在进程中创建新的线程,则可以调用 pthread_create

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

  (start_routine)(void*), void *arg);
start_routine 为新线程的入口函数, arg 为传递给 start_routine 的参数。
每个线程都有自己的线程 ID ,以便在进程内区分。线程 ID pthread_create 调用时回返给创建线程的调用者;一个线程也可以在创建后使用 pthread_self() 调用获取自己的线程 ID
pthread_self (void) ;

线程退出

线程的退出方式有三:
1 )执行完成后隐式退出;
2 )由线程本身显示调用 pthread_exit 函数退出;
pthread_exit (void * retval) ;

3 )被其他线程用 pthread_cance 函数终止:

pthread_cance (pthread_t thread) ;

在某线程中调用此函数,可以终止由参数 thread 指定的线程。
如果一个线程要等待另一个线程的终止,可以使用 pthread_join 函数,该函数的作用是调用 pthread_join 的线程将被挂起直到线程 ID 为参数 thread 的线程终止:
pthread_join (pthread_t thread, void** threadreturn);
3. 线程通信

       线程互斥

互斥意味着“排它”,即两个线程不能同时进入被互斥保护的代码。 Linux 下可以通过 pthread_mutex_t 定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。

下面的代码实现了对共享全局变量 x 用互斥体 mutex 进行保护的目的:

int x; // 进程中的全局变量

pthread_mutex_t mutex;

pthread_mutex_init(&mutex, NULL); // 按缺省的属性初始化互斥体变量 mutex

pthread_mutex_lock(&mutex); // 给互斥体变量加锁

… // 对变量 x 的操作

phtread_mutex_unlock(&mutex); // 给互斥体变量解除锁

线程同步

同步就是线程等待某个事件的发生。只有当等待的事件发生线程才继续执行,否则线程挂起并放弃处理器。当多个线程协作时,相互作用的任务必须在一定的条件下同步。

Linux 下的 C 语言编程有多种线程同步机制,最典型的是条件变量 (condition variable) pthread_cond_init 用来创建一个条件变量,其函数原型为:

pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr);

pthread_cond_wait pthread_cond_timedwait 用来等待条件变量被设置,值得注意的是这两个等待调用需要一个已经上锁的互斥体 mutex ,这是为了防止在真正进入等待状态之前别的线程有可能设置该条件变量而产生竞争。 pthread_cond_wait 的函数原型为:

pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex);

pthread_cond_broadcast 用于设置条件变量,即使得事件发生,这样等待该事件的线程将不再阻塞:

pthread_cond_broadcast (pthread_cond_t *cond) ;

pthread_cond_signal 则用于解除某一个等待线程的阻塞状态:

pthread_cond_signal (pthread_cond_t *cond) ;

pthread_cond_destroy 则用于释放一个条件变量的资源。

在头文件 semaphore.h 中定义的信号量则完成了互斥体和条件变量的封装,按照多线程程序设计中访问控制机制,控制对资源的同步访问,提供程序设计人员更方便的调用接口。

sem_init(sem_t *sem, int pshared, unsigned int val);

这个函数初始化一个信号量 sem 的值为 val ,参数 pshared 是共享属性控制,表明是否在进程间共享。

sem_wait(sem_t *sem);

调用该函数时,若 sem 为无状态,调用线程阻塞,等待信号量 sem 值增加 (post ) 成为有信号状态;若 sem 为有状态,调用线程顺序执行,但信号量的值减一。

sem_post(sem_t *sem);

调用该函数,信号量 sem 的值增加,可以从无信号状态变为有信号状态。

4. 实例

下面我们还是以著名的生产者 / 消费者问题为例来阐述 Linux 线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有 N 个,是一个环形的缓冲池。

#include <stdio.h>

#include <pthread.h>

#define BUFFER_SIZE 16 // 缓冲区数量

struct prodcons

{

  // 缓冲区相关数据结构

  int buffer[BUFFER_SIZE]; /* 实际数据存放的数组 */

  pthread_mutex_t lock; /* 互斥体 lock 用于对缓冲区的互斥操作 */

  int readpos, writepos; /* 读写指针 */

  pthread_cond_t notempty; /* 缓冲区非空的条件变量 */

  pthread_cond_t notfull; /* 缓冲区未满的条件变量 */

};

/* 初始化缓冲区结构 */

void init(struct prodcons *b)

{

  pthread_mutex_init(&b->lock, NULL);

  pthread_cond_init(&b->notempty, NULL);

  pthread_cond_init(&b->notfull, NULL);

  b->readpos = 0;

  b->writepos = 0;

}

/* 将产品放入缓冲区 , 这里是存入一个整数 */

void put(struct prodcons *b, int data)

{

  pthread_mutex_lock(&b->lock);

  /* 等待缓冲区未满 */

  if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)

  {

    pthread_cond_wait(&b->notfull, &b->lock);

  }

  /* 写数据 , 并移动指针 */

  b->buffer[b->writepos] = data;

  b->writepos++;

  if (b->writepos >  = BUFFER_SIZE)

    b->writepos = 0;

  /* 设置缓冲区非空的条件变量 */

  pthread_cond_signal(&b->notempty);

  pthread_mutex_unlock(&b->lock);

}

 

/* 从缓冲区中取出整数 */

int get(struct prodcons *b)

{

  int data;

  pthread_mutex_lock(&b->lock);

  /* 等待缓冲区非空 */

  if (b->writepos == b->readpos)

  {

    pthread_cond_wait(&b->notempty, &b->lock);

  }

  /* 读数据 , 移动读指针 */

  data = b->buffer[b->readpos];

  b->readpos++;

  if (b->readpos >  = BUFFER_SIZE)

    b->readpos = 0;

  /* 设置缓冲区未满的条件变量 */

  pthread_cond_signal(&b->notfull);

  pthread_mutex_unlock(&b->lock);

  return data;

}

 

/* 测试 : 生产者线程将 1 10000 的整数送入缓冲区 , 消费者线

程从缓冲区中获取整数 , 两者都打印信息 */

#define OVER ( - 1)

struct prodcons buffer;

void *producer(void *data)

{

  int n;

  for (n = 0; n < 10000; n++)

  {

    printf("%d --->\n", n);

    put(&buffer, n);

  } put(&buffer, OVER);

  return NULL;

}

 

void *consumer(void *data)

{

  int d;

  while (1)

  {

    d = get(&buffer);

    if (d == OVER)

      break;

    printf("--->%d \n", d);

  }

  return NULL;

}

 

int main(void)

{

  pthread_t th_a, th_b;

  void *retval;

  init(&buffer);

  /* 创建生产者和消费者线程 */

  pthread_create(&th_a, NULL, producer, 0);

  pthread_create(&th_b, NULL, consumer, 0);

  /* 等待两个线程结束 */

  pthread_join(th_a, &retval);

  pthread_join(th_b, &retval);

  return 0;

}

5.WIN32 VxWorks Linux 线程类比

目前为止,笔者已经创作了《基于嵌入式操作系统 VxWorks 的多任务并发程序设计》(《软件报》 2006 5~12 期连载)、《深入浅出 Win32 多线程程序设计》(天极网技术专题)系列,我们来找出这两个系列文章与本文的共通点。

看待技术问题要瞄准其本质,不管是 Linux VxWorks 还是 WIN32 ,其涉及到多线程的部分都是那些内容,无非就是线程控制和线程通信,它们的许多函数只是名称不同,其实质含义是等价的,下面我们来列个三大操作系统共同点详细表单:

事项

WIN32

VxWorks

Linux

线程创建

CreateThread

taskSpawn

pthread_create

线程终止

执行完成后退出;线程自身调用 ExitThread 函数即终止自己;被其他线程调用函数 TerminateThread 函数

执行完成后退出;由线程本身调用 exit 退出;被其他线程调用函数 taskDelete 终止

执行完成后退出;由线程本身调用 pthread_exit 退出;被其他线程调用函数 pthread_cance 终止

获取线程 ID

GetCurrentThreadId

taskIdSelf

pthread_self

创建互斥

CreateMutex

semMCreate

pthread_mutex_init

获取互斥

WaitForSingleObject

WaitForMultipleObjects

semTake

pthread_mutex_lock

释放互斥

ReleaseMutex

semGive

phtread_mutex_unlock

创建信号量

CreateSemaphore

semBCreate semCCreate

sem_init

等待信号量

WaitForSingleObject

semTake

sem_wait

释放信号量

ReleaseSemaphore

semGive

sem_post

6. 小结

       本章讲述了 Linux 下多线程的控制及线程间通信编程方法,给出了一个生产者 / 消费者的实例,并将 Linux 的多线程与 WIN32 VxWorks 多线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值