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

简介:


Linux下的C编程实战(四)

――“线程”控制与“线程”通信编程
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 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  定义互斥体机制完成多线程的互斥操作,该机制的作用是对某个需要互斥的部分,在进入时先得到互斥体,如果没有得到互斥体,表明互斥部分被其它线程拥有,此时欲获取互斥体的线程阻塞,直到拥有该互斥体的线程完成互斥部分的操作为止。
下面的代码实现了对共享全局变量 用互斥体 mutex  进行保护的目的:
int x; //  进程中的全局变量
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); // 按缺省的属性初始化互斥体变量 mutex
pthread_mutex_lock(&mutex); //  给互斥体变量加锁
… // 对变量 的操作
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 线程的控制和通信。一组生产者线程与一组消费者线程通过缓冲区发生联系。生产者线程将生产的产品送入缓冲区,消费者线程则从中取出产品。缓冲区有 个,是一个环形的缓冲池。
#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;
}
 
/*  测试 : 生产者线程将 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 多线程进行了类比,总结了一般规律。鉴于多线程编程已成为开发并发应用程序的主流方法,学好本章的意义也便不言自明。


 本文转自 21cnbao 51CTO博客,原文链接:http://blog.51cto.com/21cnbao/120039,如需转载请自行联系原作者



相关文章
|
7月前
|
存储 SQL 安全
Java 无锁方式实现高性能线程实战操作指南
本文深入探讨了现代高并发Java应用中单例模式的实现方式,分析了传统单例(如DCL)的局限性,并提出了多种无锁实现方案。包括基于ThreadLocal的延迟初始化、VarHandle原子操作、Record不可变对象、响应式编程(Reactor)以及CDI依赖注入等实现方式。每种方案均附有代码示例及适用场景,同时通过JMH性能测试对比各实现的优劣。最后,结合实际案例设计了一个高性能配置中心,展示了无锁单例在实际开发中的应用。总结中提出根据场景选择合适的实现方式,并遵循现代单例设计原则以优化性能和安全性。文中还提供了代码获取链接,便于读者实践与学习。
144 0
|
3月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
385 0
|
8月前
|
设计模式 运维 监控
并发设计模式实战系列(4):线程池
需要建立持续的性能剖析(Profiling)和调优机制。通过以上十二个维度的系统化扩展,构建了一个从。设置合理队列容量/拒绝策略。动态扩容/优化任务处理速度。检查线程栈定位热点代码。调整最大用户进程数限制。CPU占用率100%
549 0
|
11月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
5月前
|
数据采集 消息中间件 并行计算
Python多线程与多进程性能对比:从原理到实战的深度解析
在Python编程中,多线程与多进程是提升并发性能的关键手段。本文通过实验数据、代码示例和通俗比喻,深入解析两者在不同任务类型下的性能表现,帮助开发者科学选择并发策略,优化程序效率。
444 1
|
9月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
333 67
|
8月前
|
安全 算法 Ubuntu
Linux(openssl)环境:编程控制让证书自签的技巧。
总结:在Linux环境中,OpenSSL是一个非常实用的工具,可以帮助我们轻松地生成自签名证书。通过上述三个简单步骤,即可为内部网络、测试环境或开发环境创建自签名证书。但在公共访问场景下,建议购买经过权威认证机构签发的证书,以避免安全警告。
386 13
|
7月前
|
算法 Java 测试技术
深度优化OSS上传性能:多线程分片上传 vs 断点续传实战对比
本文深入解析对象存储服务(OSS)文件上传性能优化技术,重点探讨多线程分片上传与断点续传两种方案。通过理论分析、代码实现和性能测试,对比其在不同场景下的表现差异,并提供选型建议与最佳实践,助力提升大文件上传效率与稳定性。
750 0