说说多线程(linux、windows/c c++ mfc python java多线程)

一直在使用多线程,也学习过很多linux线程进程方面的知识(APUE UNP),有mfc里包装好的多线程,有python程序里的多线程,但是没有好好归纳过,现在好好整理归纳下关于多线程的知识。
关于多线程、多进程,参考:多进程、多线程以及如何选择?
关于线程同步(互斥)的方式与对比,参考:线程同步常用方式与区别
关于同步机制常使用的互斥锁、自旋锁、信号量、读写锁、顺序锁等等,参考:锁的种类与特点
下面开始本文
线程的概念:是计算机中独立运行的最小单位,运行时占用很少的系统资源。可以把线程看成是操作系统分配CPU时间的基本单元。一个进程可以拥有一个至多个线程。它的线程在进程内部共享地址空间、打开的文件描述符等资源。同时线程也有其私有的数据信息,包括:线程号、寄存器(程序计数器和堆栈指针)、堆栈、信号掩码、优先级、线程私有存储空间。
简单说下为什么有必要使用线程:
首先和进程比较:
1、使用多线程的理由之一是和进程相比,它是一种非常”节俭”的多任务操作方式。
2、使用多线程的理由之二是线程间方便的通信机制。
就算不和进程比较,多线程作为一种多任务、并发的机制,也有很多的优点:
1) 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。这一点我在做MFC这种有界面的项目的时候最有体会。
2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

linux多线程
相对于其他操作系统,Linux系统内核只提供了轻量级进程的支持,并未实现线程模型。Linux是一种“多进程单线程”的操作系统,Linux本身只有进程的概念,而其所谓的“线程”本质上在内核里仍然是进程。
使用:
1、线程创建

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


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

参数说明:
thread:指向pthread_create类型的指针,用于引用新创建的线程。
attr:用于设置线程的属性,一般不需要特殊的属性,所以可以简单地设置为NULL。
start_routine:传递新线程所要执行的函数地址。
arg:新线程所要执行的函数的参数。

返回值:
调用如果成功,则返回值是0;如果失败则返回错误代码。

每个线程都有自己的线程ID,以便在进程内区分。线程ID在pthread_create调用时回返给创建线程的调用者;一个线程也可以在创建后使用pthread_self()调用获取自己的线程ID:
pthread_self (void);
2、线程退出

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

pthread_exit (void * retval);

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

pthread_cancel (pthread_t thread);

如果一个线程要等待另一个线程的终止,可以使用pthread_join函数,该函数的作用是调用pthread_join的线程将被挂起直到线程ID为参数thread的线程终止:

pthread_join (pthread_t thread, void** threadreturn);

3、线程属性

/* man pthread_attr_init */
typedef struct
{
   int                    detachstate;    //是否与其他线程脱离同步
   int                    schedpolicy;    //新线程的调度策略
   struct sched_param    schedparam;        //运行优先级等
   int                    inheritsched;    //是否继承调用者线程的值
   int                    scope;            //线程竞争CPU的范围(优先级的范围)
   size_t                guardsize;        //警戒堆栈的大小
   int                    stackaddr_set;    //堆栈地址集
   void *                stackaddr;        //堆栈地址
   size_t                stacksize;        //堆栈大小
 } pthread_attr_t;

属性值不能直接设置,须使用相关函数进行操作,初始化的函数为pthread_attr_init,这个函数必须在pthread_create函数之前调用。
这里面比较重要的属性有:线程的分离状态
线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中,我们采用了线程的默认属性,即为非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。程序员应该根据自己的需要,选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二个参数可选为PTHREAD_CREATE_DETACHED(分离线程)和 PTHREAD _CREATE_JOINABLE(非分离线程)。
4、线程私有数据
创建私有数据的函数有4个:

pthread_key_create(创建)
pthread_setspecific(设置)
pthread_getspecific(获取)
pthread_key_delete(删除)

编译:

gcc -D_REENTRANT thread1.c -o thread1 –lpthread

其中-REENTRANT宏使得相关库函数(如stdio.h、errno.h中函数) 是可重入的、线程安全的(thread-safe),-lpthread则意味着链接库目录下的libpthread.a或libpthread.so文件。
_REENTRANT为我们做三件事情,并且做的非常优雅:

(1)它会对部分函数重新定义它们的可安全重入的版本,这些函数名字一般不会发生改变,只是会在函数名后面添加_r字符串,如函数名gethostbyname变成gethostbyname_r。
(2)stdio.h中原来以宏的形式实现的一些函数将变成可安全重入函数。
(3)在error.h中定义的变量error现在将成为一个函数调用,它能够以一种安全的多线程方式来获取真正的errno的值。
下面对比下,在linux下,windows下,以及实时操作系统vxworks下多线程编程:
说说多线程(linux、windows/c c++ mfc python java多线程)_第1张图片

c++多线程
C++ 不包含多线程应用程序的任何内置支持。相反,它完全依赖于操作系统来提供此功能。也就是说,我们在unix类系统下编程,就调用unix系统提供的接口,在windows下多线程编程,就使用windows提供的多线程接口。这些基本上没什么好说的,看上图就行,下面说说c++11对多线程的支持:
C++11 新标准中引入了四个头文件来支持多线程编程,他们分别是atomic ,thread,mutex,condition_variable和future
:该头文主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
:该头文件主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
:该头文件主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
:该头文件主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
:该头文件主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
下面是一个简单的使用thread类的helloworld的例子:

#include 
#include 

#include  // std::cout
#include    // std::thread

void thread_task() {
    std::cout << "hello thread" << std::endl;
}

int main(int argc, const char *argv[])
{
    std::thread t(thread_task);
    t.join();

    return EXIT_SUCCESS;
}

编写makefile文件编译helloworld例子:

all:Thread

CC=g++
CPPFLAGS=-Wall -std=c++11 -ggdb
LDFLAGS=-pthread

Thread:Thread.o
    $(CC) $(LDFLAGS) -o $@ $^

Thread.o:Thread.cc
    $(CC) $(CPPFLAGS) -o $@ -c $^


.PHONY:
    clean

clean:
    rm Thread.o Thread

GCC 暂时默认没有加载 pthread 库,所以编译的时候要加上 -pthread选项。

简单使用std::mutex:

std::mutex m;
int j = 0;
void foo() 
  { 
  m.lock();        // 进入临界区域
  j++;
  m.unlock();      // 离开
  }
void func()
  {
  std::thread t1(foo); 
  std::thread t2(foo); 
  t1.join();
  t2.join();
 // j = 2;
} 

Windows下多线程编程
很显然,MFC是微软基础类,首先是基于windows平台,其次,mfc是使用了c++语言的类库,所以其多线程可以参考windows下多线程的使用。

在Windows下你可以调用Win SDK的API来创建一个线程,也可以用使用C Run-Time Library中的函数来创建,还可以使用MFC封装的相关线程函数来得到你想要的线程。

1、windows自带 api函数创建线程:

HANDLE
WINAPI
CreateThread(
    __in_opt  LPSECURITY_ATTRIBUTES lpThreadAttributes,
    __in      SIZE_T dwStackSize,
    __in      LPTHREAD_START_ROUTINE lpStartAddress,
    __in_opt  LPVOID lpParameter,
    __in      DWORD dwCreationFlags,
    __out_opt LPDWORD lpThreadId
    );

2、c运行时库创建线程:

uintptr_t __cdecl _beginthreadex(_In_opt_ void * _Security,
                                 _In_ unsigned _StackSize,
                                 _In_ unsigned (__stdcall * _StartAddress) (void *),
                                 _In_opt_ void * _ArgList, 
                                 _In_ unsigned _InitFlag,
                                 _In_opt_ unsigned * _ThrdAddr);

3、mfc封装的多线程:
如果要在MFC程序中产生一个线程,而该线程将调用MFC函数或使用MFC的任何数据,那么必须以AfxBeginThread()或CWinThread::CreateThrad()来产生这些线程。而MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。
工作线程的创建如下:

CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
                      LPVOID pParam,
                      nPriority=THREAD_PRIORITY_NORMAL,
                      UINT nStackSize=0,
                      DWORD dwCreateFlags=0,
                      LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明:UINT ExecutingFunction(LPVOID pParam);

用户界面线程的创建如下:

CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
                      int nPriority=THREAD_PRIORITY_NORMAL,
                      UINT nStackSize=0,
                      DWORD dwCreateFlags=0,
                      LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;

 
python多线程
python多线程就相对简单明了很多,有2种方式实现多线程:
1、调用thread模块中的start_new_thread()函数来产生新线程。
线程的结束可以等待线程自然结束,也可以在线程函数中调用thread.exit()或thread.exit_thread()方法。
2、 创建threading.Thread的子类来包装一个线程对象
即创建自己的线程类,必要时重写threading.Thread类的方法,线程的控制可以由自己定制。

threading.Thread类的使用:
1,在自己的线程类的init里调用threading.Thread.init(self, name = threadname)
Threadname为线程的名字
2, run(),通常需要重写,编写代码实现做需要的功能。
注意了,start()作用是启动线程活动。在每个线程对象中最多被调用一次。它安排对象的run() 被调用在一单独的控制线程中。
3,getName(),获得线程对象名称
4,setName(),设置线程对象名称
5,start(),启动线程
6,join([timeout])
等待至线程中止。阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
timeout参数不是None,它应当是浮点数指明以秒计的操作超时值。因为join()总是返回None,你必须调用isAlive()来判别超时是否发生。
当timeout 参数没有被指定或者是None时,操作将被阻塞直至线程中止。
线程能被join()许多次。
线程不能调用自身的join(),因为这将会引起死锁。
在线程启动之前尝试调用join()会发生错误。
7,setDaemon(bool),设置子线程是否随主线程一起结束,必须在start()之前调用。默认为False。
8,isDaemon(),判断线程是否随主线程一起结束。
9,isAlive(),检查线程是否在运行中。

详细内容可以参考官方doc文档,也可以参考我的另一篇文章:python:threading.Thread类的使用详解

最后简单说下java多线程
网上说:

Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。

我觉得很类似于python,要么使用系统thread类xx接口,要么重写重新包装。说的不对请见谅,本人不懂java,,,qaq

你可能感兴趣的:(网络编程)