浅析C++编程当中的线程_C 语言

线程的概念

C++中的线程的Text Segment和Data Segment都是共享的,如果定义一个函数,在各线程中都可以调用,如果定义一个全局变量,在各线程中都可以访问到。除此之外,各线程还共享以下进程资源和环境:

  •     文件描述符
  •     每种信号的处理方式
  •     当前工作目录
  •     用户id和组id

但是,有些资源是每个线程各有一份的:

  •     线程id
  •     上下文,包括各种寄存器的值、程序计数器和栈指针
  •     栈空间
  •     errno变量
  •     信号屏蔽字
  •     调度优先级

我们将要学习的线程库函数是由POSIX标准定义的,称为POSIX thread或pthread。
线程控制
创建线程

创建线程的函数原型如下:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

返回值:成功返回0,失败返回错误号。

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。start_routine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数类型为void*,这个指针按什么类型解释由调用者自己定义。start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_routine返回时,这个线程就退出了,其它线程可以调用pthread_join得到start_routine的返回值。

pthread_create成功返回后,新创建的线程的id被填写到thread参数所指向的内存单元。我们知道进程id的类型是pid_t,每个进程的id在整个系统中是唯一的,调用getpid可以得到当前进程的id,是一个正整数值。线程id的类型是thread_t,它只在当前进程中保证是唯一的,在不同的系统中thread_t这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,也可能是一个地址,所以不能简单的当成整数用printf打印,调用pthread_self可以获取当前线程的id。

我们先来写一个简单的例子:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

pthread_t ntid;

void printids(const void *t)
{
    char *s = (char *)t;
  pid_t   pid;
  pthread_t tid;

  pid = getpid();
  tid = pthread_self();
  printf("%s pid %u tid %u (0x%x)\n", s, (unsigned int)pid,
      (unsigned int)tid, (unsigned int)tid);
}

void *thr_fn(void *arg)
{
  printids(arg);
  return NULL;
}

int main(void)
{
  int err;

  err = pthread_create(&ntid, NULL, thr_fn, (void *)"Child Process:");
  if (err != 0) {
    fprintf(stderr, "can't create thread: %s\n", strerror(err));
    exit(1);
  }
  printids("main thread:");
  sleep(1);

  return 0;
}

编译执行结果如下:

g++ thread.cpp -o thread -lpthread
./thread
main thread: pid 21046 tid 3612727104 (0xd755d740)
Child Process: pid 21046 tid 3604444928 (0xd6d77700)

从结果可以知道,thread_t类型是一个地址值,属于同一进程的多个线程调用getpid可以得到相同的进程号,而调用pthread_self得到的线程号各不相同。

如果任意一个线程调用了exit或_exit,则整个进程的所有线程都终止,由于从main函数return也相当于调用exit,为了防止新创建的线程还没有得到执行就终止,我们在main函数return之前延时1秒,这只是一种权宜之计,即使主线程等待1秒,内核也不一定会调度新创建的线程执行,接下来,我们学习一下比较好的解决方法。
终止线程

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

  1.     从线程函数return。这种方法对主线程不适应,从main函数return相当于调用exit。
  2.     一个线程可以调用pthread_cancel终止同一个进程中的另一个线程。
  3.     线程可以调用pthread_exit终止自己。

这里主要介绍pthread_exit和pthread_join的用法。

#include <pthread.h>

void pthread_exit(void *value_ptr);

value_ptr是void*类型,和线程函数返回值的用法一样,其它线程可以调用pthread_join获取这个指针。
需要注意,pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了。

#include <pthread.h>

int pthread_join(pthread_t thread, void **value_ptr);

返回值:成功返回0,失败返回错误号。

调用该函数的线程将挂起等待,直到id为thread的线程终止。thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的,总结如下:

  •     如果thread线程通过return返回,value_ptr所指向的单元里存放的是thread线程函数的返回值。
  •     如果thread线程被别的线程调用pthread_cancel异常终止掉,value_ptr所指向的单元存放的是常数PTHREAD_CANCELED。
  •     如果thread线程是自己调用pthread_exit终止的,value_ptr所指向的单元存放的是传给pthread_exit的参数。

如果对thread线程的终止状态不感兴趣,可以传NULL给value_ptr参数。参考代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void* thread_function_1(void *arg)
{
  printf("thread 1 running\n");
  return (void *)1;
}

void* thread_function_2(void *arg)
{
  printf("thread 2 exiting\n");
  pthread_exit((void *) 2);
}

void* thread_function_3(void* arg)
{
  while (1) {
    printf("thread 3 writeing\n");
    sleep(1);
  }
}

int main(void)
{
  pthread_t tid;
  void *tret;

  pthread_create(&tid, NULL, thread_function_1, NULL);
  pthread_join(tid, &tret);
  printf("thread 1 exit code %d\n", *((int*) (&tret)));

  pthread_create(&tid, NULL, thread_function_2, NULL);
  pthread_join(tid, &tret);
  printf("thread 2 exit code %d\n", *((int*) (&tret)));

  pthread_create(&tid, NULL, thread_function_3, NULL);
  sleep(3);
  pthread_cancel(tid);
  pthread_join(tid, &tret);
  printf("thread 3 exit code %d\n", *((int*) (&tret)));

  return 0;
}

运行结果是:

thread 1 running
thread 1 exit code 1
thread 2 exiting
thread 2 exit code 2
thread 3 writeing
thread 3 writeing
thread 3 writeing
thread 3 exit code -1

可见,Linux的pthread库中常数PTHREAD_CANCELED的值是-1.可以在头文件pthread.h中找到它的定义:

#define PTHREAD_CANCELED ((void *) -1)

线程间同步

多个线程同时访问共享数据时可能会冲突,例如两个线程都要把某个全局变量增加1,这个操作在某平台上需要三条指令才能完成:

  •     从内存读变量值到寄存器。
  •     寄存器值加1.
  •     将寄存器的值写回到内存。

这个时候很容易出现两个进程同时操作寄存器变量值的情况,导致最终结果不正确。

解决的办法是引入互斥锁(Mutex, Mutual Exclusive Lock),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其它线程,没有获得锁的线程只能等待而不能访问共享数据,这样,“读-修改-写”的三步操作组成一个原子操作,要不都执行,要不都不执行,不会执行到中间被打断,也不会在其它处理器上并行做这个操作。

Mutex用pthread_mutex_t类型的变量表示,可以这样初始化和销毁:

#include <pthread.h>

int pthread_mutex_destory(pthread_mutex_t *mutex);
int pthread_mutex_int(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
pthread_mutex_t mutex = PTHEAD_MUTEX_INITIALIZER;

返回值:成功返回0,失败返回错误号。

用pthread_mutex_init函数初始化的Mutex可以用pthread_mutex_destroy销毁。如果Mutex变量是静态分配的(全局变量或static变量),也可以用宏定义PTHREAD_MUTEX_INITIALIZER来初始化,相当于用pthread_mutex_init初始化并且attr参数为NULL。Mutex的加锁和解锁操作可以用下列函数:

#include <pthread.h>

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

返回值:成功返回0,失败返回错误号。

一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经调用pthread_mutex_lock获得了该Mutex,则当前线程需要挂起等待,直到另一个线程调用pthread_mutex_unlock释放Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。

我们用Mutex解决上面说的两个线程同时对全局变量+1可能导致紊乱的问题:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NLOOP 5000

int counter;
pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;

void *do_add_process(void *vptr)
{
  int i, val;

  for (i = 0; i < NLOOP; i ++) {
    pthread_mutex_lock(&counter_mutex);
    val = counter;
    printf("%x:%d\n", (unsigned int)pthread_self(), val + 1);
    counter = val + 1;
    pthread_mutex_unlock(&counter_mutex);
  }

  return NULL;
}

int main()
{
  pthread_t tida, tidb;

  pthread_create(&tida, NULL, do_add_process, NULL);
  pthread_create(&tidb, NULL, do_add_process, NULL);

  pthread_join(tida, NULL);
  pthread_join(tidb, NULL);

  return 0;
}

这样,每次运行都能显示到10000。如果去掉锁机制,可能就会有问题。这个机制类似于Java的synchronized块机制。
Condition Variable

线程间的同步还有这样一种情况:线程A需要等某个条件成立才能继续往下执行,现在这个条件不成立,线程A就阻塞等待,而线程B在执行过程中使这个条件成立了,就唤醒线程A继续执行。在pthread库中通过条件变量(Conditiion Variable)来阻塞等待一个条件,或者唤醒等待这个条件的线程。Condition Variable用pthread_cond_t类型的变量表示,可以这样初始化和销毁:

#include <pthread.h>

int pthread_cond_destory(pthread_cond_t *cond);
int pthread_cond_init(pthead_cond_t *cond, const pthread_condattr_t *attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

返回值:成功返回0,失败返回错误号。

和Mutex的初始化和销毁类似,pthread_cond_init函数初始化一个Condition Variable,attr参数为NULL则表示缺省属性,pthread_cond_destroy函数销毁一个Condition Variable。如果Condition Variable是静态分配的,也可以用宏定义PTHEAD_COND_INITIALIZER初始化,相当于用pthread_cond_init函数初始化并且attr参数为NULL。Condition Variable的操作可以用下列函数:

#include <pthread.h>

int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

可见,一个Condition Variable总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在一个Condition Variable上阻塞等待,这个函数做以下三步操作:

  1.     释放Mutex。
  2.     阻塞等待。
  3.     当被唤醒时,重新获得Mutex并返回。

pthread_cond_timedwait函数还有一个额外的参数可以设定等待超时,如果到达了abstime所指定的时刻仍然没有别的线程来唤醒当前线程,就返回ETIMEDOUT。一个线程可以调用pthread_cond_signal唤醒在某个Condition Variable上等待的另一个线程,也可以调用pthread_cond_broadcast唤醒在这个Condition Variable上等待的所有线程。

下面的程序演示了一个生产者-消费者的例子,生产者生产一个结构体串在链表的表头上,消费者从表头取走结构体。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

struct msg {
  struct msg *next;
  int num;
};

struct msg *head;
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

void* consumer(void *p)
{
  struct msg *mp;

  for(;;) {
    pthread_mutex_lock(&lock);
    while (head == NULL) {
      pthread_cond_wait(&has_product, &lock);
    }
    mp = head;
    head = mp->next;
    pthread_mutex_unlock(&lock);
    printf("Consume %d\n", mp->num);
    free(mp);
    sleep(rand() % 5);
  }
}

void* producer(void *p)
{
  struct msg *mp;

  for(;;) {
    mp = (struct msg *)malloc(sizeof(*mp));
    pthread_mutex_lock(&lock);
    mp->next = head;
    mp->num = rand() % 1000;
    head = mp;
    printf("Product %d\n", mp->num);
    pthread_mutex_unlock(&lock);
    pthread_cond_signal(&has_product);
    sleep(rand() % 5);
  }
}

int main()
{
  pthread_t pid, cid;
  srand(time(NULL));

  pthread_create(&pid, NULL, producer, NULL);
  pthread_create(&cid, NULL, consumer, NULL);

  pthread_join(pid, NULL);
  pthread_join(cid, NULL);

  return 0;
}

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索c++
线程
c语言多线程编程、c语言多线程编程实例、c语言多线程编程入门、多线程编程语言、c 多线程编程,以便于您获取更多的相关知识。

时间: 2024-11-02 03:39:14

浅析C++编程当中的线程_C 语言的相关文章

浅析设计模式中的代理模式在C++编程中的运用_C 语言

由遇到的问题引出代理模式 至少在以下集中情况下可以用代理模式解决问题: 创建开销大的对象时候,比如显示一幅大的图片,我们将这个创建的过程交给代理去完成,GoF 称之为虚代理(Virtual Proxy): 为网络上的对象创建一个局部的本地代理,比如要操作一个网络上的一个对象(网络性能不好的时候,问题尤其突出),我们将这个操纵的过程交给一个代理去完成,GoF 称之为远程代理(Remote Proxy): 对对象进行控制访问的时候,比如在 Jive 论坛中不同权限的用户(如管理员.普通用户等)将获得

C++多线程编程时的数据保护_C 语言

 在编写多线程程序时,多个线程同时访问某个共享资源,会导致同步的问题,这篇文章中我们将介绍 C++11 多线程编程中的数据保护.数据丢失 让我们从一个简单的例子开始,请看如下代码:   #include <iostream> #include <string> #include <thread> #include <vector> using std::thread; using std::vector; using std::cout; using std

详解C++编程中的析构函数_C 语言

C++析构函数 创建对象时系统会自动调用构造函数进行初始化工作,同样,销毁对象时系统也会自动调用一个函数来进行清理工作(例如回收创建对象时消耗的各种资源),这个函数被称为析构函数. 析构函数(Destructor)也是一种特殊的成员函数,没有返回值,不需要用户调用,而是在销毁对象时自动执行.与构造函数不同的是,析构函数的名字是在类名前面加一个"~"符号. 注意:析构函数没有参数,不能被重载,因此一个类只能有一个析构函数.如果用户没有定义,那么编译器会自动生成. 析构函数举例: #inc

C++ 关于MFC多线程编程的注意事项_C 语言

在多线程编程中,最简单的方法,无非就是利用 AfxBeginThread  来创建一个工作线程,看一下这个函数的说明: 复制代码 代码如下: CWinThread* AFXAPI AfxBeginThread( AFX_THREADPROC pfnThreadProc, LPVOID pParam, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_AT

DSP中浮点转定点运算--举例及编程中的心得_C 语言

5.举例及编程中的心得 5.1举例 "第3章  DSP芯片的定点运算.doc"这篇文章中给了一个很简单有能说明问题的例子,不想动大脑了,直接引用过来如下. 这是一个对语音信号(0.3kHz~3.4kHz)进行低通滤波的C语言程序,低通滤波的截止频率为800Hz,滤波器采用19点的有限冲击响应FIR滤波.语音信号的采样频率为8kHz,每个语音样值按16位整型数存放在insp.dat文件中. 例3.7  语音信号800Hz 19点FIR低通滤波C语言浮点程序 复制代码 代码如下: #inc

举例解析设计模式中的工厂方法模式在C++编程中的运用_C 语言

工厂方法模式不同于简单工厂模式的地方在于工厂方法模式把对象的创建过程放到里子类里.这样工厂父对象和产品父对象一样,可以是抽象类或者接口,只定义相应的规范或操作,不涉及具体的创建或实现细节. 其类图如下: 实例代码为: #pragma once class IProduct { public: IProduct(void); virtual ~IProduct(void); }; #pragma once #include "iproduct.h" class IPad : public

c语言socket多线程编程限制客户端连接数_C 语言

先上一些多线程需要使用的函数定义: 复制代码 代码如下: DWORD WINAPI ProcessClientRequests(LPVOID lpParam)  //新线程将会执行的函数定义{      return 0;} HANDLE handler=CreateThread(NULL, 0, ProcessClientRequests, &clientsocket, 0, NULL);   //这里比较简单,&clientsocket是个指针,是从主线程传入新线程的参数 WaitFo

浅析C++中的虚函数_C 语言

一.定义定义:在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数. 语法:virtual 函数返回类型函数名(参数表) { 函数体 } 用途:实现多态性,通过指向派生类的基类指针,访问派生类中同名覆盖成员函数 虚函数必须是基类的非静态成员函数,其访问权限可以是protected或public. 定义为virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数. 二.作用虚函数的作用是实现动态联编,也就是在程序的运行阶段动态地选择合适的成员函

浅析直接插入排序与折半插入排序_C 语言

首先看一下例子,将数据一个个的插入到一个列表中,插入后这个列表就排序好了 注意:这个列表是递增的,而且内存空间已分配好,只是没有填充真正的数据,如下代码: 复制代码 代码如下: int InsertSort(MergeType *L, int data){ int j;  if (!L->len) {  L->elem[++L->len] = data;  return 0; }  for (j = L->len-1; j >= 0; --j) {  if (data <