2022/06/27

安裝project-open

project-open 有提供 docker image,也可以直接安裝,以下記錄安裝 project-open 的過程。

CentOS7

參考網頁

http://www.project-open.com/en/install-rhel-7

  1. system tools

    timedatectl set-timezone Asia/Taipei
    
    yum -y install system-config-*
    
    yum -y group install "System Administration Tools" --setopt=group_package_types=mandatory,default,optional
  2. KDE Plasma Workspaces Install "KDE Plasma Workspaces" Graphical Environment GNOME 3 on CentOS 7 has a number of important issues, so the ]po[ team recommends to switch to KDE:

    #yum -y group install "KDE Plasma Workspaces" "X Window System"
    #yum -y group install "Graphical Administration Tools" --setopt=group_package_types=mandatory,default,optional
    yum -y install net-tools setools policycoreutils-python
    #ln -sf /lib/systemd/system/graphical.target /etc/systemd/system/default.target
  3. Development Tools

    yum -y group install "Development Tools" --setopt=group_package_types=mandatory,default,optional
    yum -y group install "Compatibility Libraries"
    yum -y install vim emacs-nox
    yum -y install cvs svn git wget libXaw expat expat-devel pango graphviz graphviz-devel ImageMagick
    yum -y install libdbi-dbd-pgsql openldap-clients openldap-devel mlocate sharutils psmisc
  4. Perl Libraries

    yum -y install graphviz-perl perl perl-Archive-Tar perl-Archive-Zip perl-CGI perl-CGI-Session
    yum -y install perl-CPAN perl-CPAN-Changes perl-CPAN-Meta perl-CPAN-Meta-Requirements perl-CPAN-Meta-YAML
    yum -y install perl-Carp perl-Compress-Raw-Bzip2 perl-Crypt-DES perl-Crypt-OpenSSL-RSA
    yum -y install perl-Crypt-OpenSSL-Random perl-Crypt-PasswdMD5 perl-Crypt-SSLeay perl-DBD-Pg
    yum -y install perl-DBD-Pg-tests perl-DBI perl-Data-Dumper perl-DateTime perl-Digest-MD5
    yum -y install perl-Encode perl-File-Slurp perl-GSSAPI perl-IO-Socket-IP perl-IO-Socket-SSL
    yum -y install perl-JSON perl-LDAP perl-LWP-MediaTypes perl-LWP-Protocol-https perl-Net-DNS
    yum -y install perl-Net-HTTP perl-Net-SSLeay perl-Params-Check perl-Params-Util perl-Params-Validate
    yum -y install perl-Socket perl-TimeDate perl-WWW-Curl perl-YAML perl-core perl-devel perl-gettext
    yum -y install perl-libs perl-libwww-perl rrdtool-perl perl-YAML
  5. OpenOffce/LibreOffice

    yum -y install libreoffice libreoffice-headless
  6. 將 projop 加入 wheel

    vi /etc/group
    
    wheel:x:10:projop
  7. 修改 hosts

    vi /etc/hosts
    
    127.0.0.1 localhost
    10.140.0.3 po
  8. download files

    wget http://sourceforge.net/projects/project-open/files/project-open/Support%20Files/naviserver-4.99.8.tgz 
    wget http://sourceforge.net/projects/project-open/files/project-open/Support%20Files/web_projop-aux-files.5.0.0.0.0.tgz 
    wget http://sourceforge.net/projects/project-open/files/project-open/V5.0/update/project-open-Update-5.0.3.0.0.tgz 
  9. po installer

    groupadd projop                                                              # create a group called "projop"
    mkdir /web/                                                                  # super-directory for all Web servers /web/ by default
    useradd -d /web/projop -g projop projop                                      # create user "projop" with home directory /web/projop
    cd /web/projop/
    tar xzf /usr/src/web_projop-aux-files.5.0.0.0.0.tgz                          # extract auxillary files
    tar xzf /usr/src/project-open-Update-5.0.3.0.0.tgz                           # extract the ]po[ product source code - latest
    chown -R projop:projop /web/projop                                           # set ownership to all files
    
    cd /usr/local
    tar xzf /usr/src/naviserver-4.99.8.tgz                                       # extract the NaviServer binary 64 bit
  10. Setup PostgreSQL 9.2

    yum -y install postgresql postgresql-server postgresql-contrib postgresql-devel postgresql-odbc postgresql-docs
    
    systemctl enable postgresql

    init PostgreSQL

    /usr/bin/postgresql-setup initdb
    systemctl start postgresql
  11. Database

    su - postgres -c "createuser -s projop"                             # database user "projop" with admin rights
    su - projop -c "createdb --encoding=utf8 --owner=projop projop"     # new database
    su - projop -c "createlang plpgsql projop"                          # enable PlPg/SQL, may already be installed
  12. verify

    su - projop -c psql
    
    # Enter "\q" or press Ctrl-D to exit

    匯入 DB sql

    # import db
    
    su - projop
    psql -f ~/pg_dump.5.0.3.0.0.sql > import.log 2>&1
    
    # verify
    
    psql -c "select count(*) from users"
  13. 修改 postgresql.conf

    vi /var/lib/pgsql/data/postgresql.conf
    
    listen_addresses = '*'
    max_connections = 100
    
    #shared_buffers = 512MB
    shared_buffers = 256MB
    
    work_mem = 64MB
    maintenance_work_mem = 16MB
    
    checkpoint_segments = 64
    
    log_timezone = 'Asia/Taipei'
    timezone = 'Asia/Taipei'
  14. update pg_hba.conf

    vi /var/lib/pgsql/data/pg_hba.conf
    
    local   all             all                                     peer
    host    all             all             127.0.0.1/32            trust
    host    all             all             ::1/128                 ident
  15. update po config

    vi /web/projop/etc/config.tcl
    
    set httpport            8000
    set httpsport           8443
    
    set servername   "maxkit \]project-open\[ Server"
    set homedir      /usr/local/ns
  16. manual startup

    /usr/local/ns/bin/nsd -f -t /web/projop/etc/config.tcl -u projop -g projop
  17. service

    vi /usr/lib/systemd/system/projop.service
    
    [Unit]
    Description=NaviServer Web Server as user projop
    After=postgresql.service network.target
    Wants=postgresql.service
    
    [Service]
    Type=forking
    PIDFile=/web/projop/log/nsd.pid
    
    ExecStartPre=/usr/bin/rm -f /web/projop/log/nsd.pid
    ExecStart=/usr/local/ns/bin/nsd -t /web/projop/etc/config.tcl -u projop -g projop &
    ExecReload=/bin/kill -s HUP $MAINPID
    ExecStop=/bin/kill -s 9 $MAINPID
    
    Restart=always
    
    # Restart=on-abort
    
    # Restart=on-abnormal
    
    KillMode=process
    
    [Install]
    WantedBy=multi-user.target
    # init service
    chmod 755 /usr/lib/systemd/system/projop.service
    systemctl daemon-reload
    systemctl enable projop.service
    systemctl start projop.service
    
    tail -f /web/projop/log/error.log
  18. nginx

    yum -y install epel-release
    yum -y install nginx
    vi /etc/nginx/nginx.conf
    
    user nginx;
    worker_processes auto;
    error_log /var/log/nginx/error.log;
    pid /run/nginx.pid;
    
    events {
       worker_connections 1024;
    }
    
    http {
       log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                         '$status $body_bytes_sent "$http_referer" '
                         '"$http_user_agent" "$http_x_forwarded_for"';
       access_log          /var/log/nginx/access.log  main;
       sendfile            on;
       tcp_nopush          on;
       tcp_nodelay         on;
       keepalive_timeout   601;
       types_hash_max_size 2048;
       default_type        application/octet-stream;
       include             /etc/nginx/mime.types;
       include             /etc/nginx/conf.d/*.conf;
    
       server {
           listen 80;
           location / {
               # pass all communication to NaviServer on port 8000
               proxy_pass           http://127.0.0.1:8000;
               # add information about the original IP
               proxy_set_header     X-Forwarded-For $remote_addr;
               # upload files to file storage up to 1G
               client_max_body_size 1024M;
           }
    
           # error_page    500 502 503 504 /err/50x.html;
           # error_page    404             /err/404.html;
           # location /err/ {
           #     root /usr/share/nginx/html;
           # }
    
       }
    
    }

    先用 port 80 瀏覽一次網頁

    cat /var/log/audit/audit.log | grep nginx | grep denied | audit2allow -M mynginx
    semodule -i mynginx.pp

Add Swap to CentOS 7

ref: How to Add Swap Space on CentOS 7 - Google Cloud

```
# 1G

dd if=/dev/zero of=/swapfile bs=1024 count=1048576

# 2G

dd if=/dev/zero of=/swapfile bs=1024 count=2097152

# 4G

dd if=/dev/zero of=/swapfile bs=1024 count=4194304

chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

swapon --show

```

```
vi /etc/fstab

/swapfile swap swap defaults 0 0

```

Docker

ref: http://www.project-open.com/en/install-docker-centos7

  1. CentOS 7安裝 docker

    yum install -y yum-utils device-mapper-persistent-data lvm2
    yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
    yum install docker-ce
    
    systemctl enable docker
    systemctl start docker
  2. 下載 project-open

    mkdir -p download/project-open
    
    cd download/project-open
    
    wget --no-check-certificate https://sourceforge.net/projects/project-open/files/project-open/V5.0/older/project-open-Docker-Community-5.0.2.4.0.beta5.zip/download -O project-open-Docker-Community-5.0.2.4.0.beta5.zip
    
    unzip project-open-Docker-Community-5.0.2.4.0.beta5.zip
    cd po5-centos7
  3. 修改 setup line 24

    if ! wget --no-check-certificate -nv "$url" ; then
  4. 安裝

    ./setup
    ./build
    
    docker images
    
    REPOSITORY TAG IMAGE ID CREATED SIZE
    local/po5-centos7 latest acc4736e6a0c 7 minutes ago 1.81GB
  5. 啟動

    ./run
    
    docker start po5_centos7
    docker update --restart unless-stopped po5_centos7

2022/06/13

C# Thread

Thread 相關類別

有三個相關的類別:Thread, ThreadStart, ParameterizedThreadStart

  • ThreadStart

    宣告的 function會被 thread 執行,ThreadStart 負責建立無參數的委派函式

  • ParameterizedThreadStart

    宣告的 function會被 thread 執行,ThreadStart 負責建立有參數的委派函式

  • Thread

    建立、控制、管理 Thread,常用的屬性:

    屬性 說明 回傳資料型別
    CurrentThread 目前正在執行的 thread Thread
    IsAlive thread 的執行狀態 Boolean
    IsBackground thread 是否為背景執行緒 Boolean
    ManagedThreadId thread 的識別號碼 Integer
    Name thread 的名稱 String 或 null
    ThreadState thread 的狀態 ThreadState (enum)

    常用 method

    method 說明
    Abort() 停止 thread,停止後,無法重新啟動
    BeginCriticalRegion() 設定 Critical region
    EndCriticalRegion() 結束 Critical region
    Interrupt() 中斷 WaitSleepJoin 狀態的 thread
    Join([a]) 封鎖執行緒直到停止執行為止,a 為 integer (ms) 或 TimeSpan
    ResetAbort() 取消正在要求的 Abort()
    Sleep(a) 暫停執行緒 a (ms) 或 TimeSpan
    Start([a]) 啟動 thread,a為委派函式的參數,object 型別

    Note: 使用 Abort() 不保證一定能停止 thread,有可能會發生 exception。ex: SecurityException: 沒有權限停止 thread,ThreadStateException: 停止已暫停的 thread

  • ThreadState (enum)

    位於 System.Threading namespace

    列舉常數 value 說明
    Aborted 256 執行緒目前無作用,但狀態尚未變更為 Stopped
    AbortRequested 128 已收到 Abort()
    Background 4 背景執行
    Running 0 正在執行
    Stopped 16 已停止
    StopRequested 1 已被要求停止中
    Suspended 64 已暫停
    SuspendedRequested 2 已被要求暫停中
    Unstarted 8 還沒開始執行,未被呼叫 Start()
    WaitSleepJoin 32 已被封鎖

    列出呼叫哪個 method 會導致狀態改變

    method state
    建立 thread Unstarted
    Start() Running
    Sleep() WaitSleepJoin
    對另一個物件呼叫 Monitor.Wait() WaitSleepJoin
    Join WaitSleepJoin
    Interrupt() Runing
    Suspend() SuspendedRequested
    回應 Suspend() Suspended
    Resume() Running
    Abort() AbortRequested
    回應 Abort() Stopped
    thread 已終止 Stopped

建立 thread

  1. 建立委派的 method

  2. 使用 ThreadStart 建立委派物件

  3. 使用委派物件建立 Thread 型別的 Thread 物件

  4. Start()

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        bool fgDone;
        Int32 sum;
        int guess;

        public Form1()
        {
            InitializeComponent();
        }

        void count()
        {            
            while (sum < int.MaxValue)
                sum++;

            fgDone = true;
        }

        void count_param(object num)
        {
            Random rd = new Random();

            while(guess!=(int)num)
            {
                guess = rd.Next(1, 101);
                Thread.Sleep(100);
            }                     

            fgDone = true;
        }

        // 沒有用 thread,計算過程中,視窗會卡住,無法使用
        private void Button1_Click(object sender, EventArgs e)
        {          
            textBox1.AppendText("開始計算\r\n");
            sum = 0;

            while (sum < int.MaxValue)
                sum++;

            textBox1.AppendText("計算完畢,sum= " + 
                sum.ToString()+"\r\n");
        }

        // 用 ThreadStart 產生 Thread
        private void button2_Click(object sender, EventArgs e)
        {
            ThreadStart thdStart = new ThreadStart(count);
            Thread thd = new Thread(thdStart);

            sum = 0;
            fgDone = false;
            textBox1.AppendText("執行緒開始執行\r\n");

            thd.Start();
            timer1.Enabled = true;
        }

        // 用 ParameterizedThreadStart 產生 Thread
        private void button3_Click(object sender, EventArgs e)
        {
            int num = 78;
            ParameterizedThreadStart paramStart = 
                new ParameterizedThreadStart(count_param);
            Thread thd = new Thread(paramStart);

            fgDone = false;
            guess = -1;
            thd.Start(num);
            timer2.Enabled = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (fgDone)
            {
                timer1.Enabled = false;
                textBox1.AppendText("計算完畢,sum= " +
                    sum.ToString()+"\r\n");
            }
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox1.AppendText(guess.ToString() + "\r\n");
            if (fgDone)
            {
                timer2.Enabled = false;
                textBox1.AppendText("找到了\r\n");
            }
        }
    }
}

取得 thread 執行結果

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Int32 sum;

        public Form1()
        {
            InitializeComponent();
        }

        void count()
        {
            while (sum < int.MaxValue)
                sum++;           
        }

        void count_param(object param)
        {
            Int32[] pp = (Int32[])param;

            while (pp[0] < int.MaxValue)
                pp[0]++;           
        }

        // 透過 sum 全域變數,儲存 thread 的執行結果
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count);

            sum = 0;
            textBox1.AppendText("執行緒開始執行\r\n");

            thd.Start();
            textBox1.AppendText("使用Join()方法,"+"" +
                "所以必須等待執行緒執行結束...\r\n");
            thd.Join();
            textBox1.AppendText("sum=" + sum.ToString() + 
                "\r\n");            
        }

        // 透過傳入 thread 的參數,儲存 thread 的執行結果
        // 因為該參數是 array,傳入是 call by value
        // 會將 sum1[0] 的記憶體位址傳給該 method
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_param);
            Int32 []sum1 = { 0 };

            textBox1.AppendText("執行緒開始執行\r\n");
            thd.Start(sum1);
            textBox1.AppendText("使用Join()方法," + "" +
                "所以必須等待執行緒執行結束...\r\n");
            thd.Join();
            textBox1.AppendText("sum1=" + sum1[0].ToString() + 
                "\r\n");
        }
    }
}

thread 生命週期

Thread 提供 Abort(), Join(), Interrupt(), Sleep(), Suspend(), Resume(),其中 Suspend(), Resume() 已經建議不要使用。

Join() 會等待 thread 結束,呼叫 Join() 的 thread 會持續等待無法回應。

結束 Thread 的方法,是讓該 Thread 完工後自行結束,如果是持續工作的 Thread,就要用 Abort(),或是用全域變數判斷要不要繼續執行。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        bool fgDone_a, fgDone_b;
        int guess_a, guess_b;
        Thread th_a=null;
        bool fgRun;
        int num_a = 78, num_b=50;

        public Form1()
        {
            InitializeComponent();
        }

        // 亂數產生數字,直到該數字為 78
        // 結束時,設定 fgDone_a,並停止 timer1
        void count_a()
        {
            Random rd = new Random();
            try
            {
                while (guess_a != num_a)
                {
                    guess_a = rd.Next(1, 101);
                    Thread.Sleep(100);
                }

                fgDone_a = true;
            }
            catch (ThreadAbortException ex)
            {
                timer1.Enabled = false;
            }
            catch (ThreadInterruptedException ex)
            {
                timer1.Enabled = false;
            }
        }

        void count_b()
        {
            Random rd = new Random();

            while (guess_b != num_b && fgRun)
            {
                guess_b = rd.Next(1, 101);
                Thread.Sleep(100);
            }

            fgDone_b = true;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            textBox1.AppendText(guess_a.ToString() + "\r\n");
            if (fgDone_a)
            {
                timer1.Enabled = false;
                textBox1.AppendText("找到了\r\n");
            }
        }

        // 用 count_a 產生 Thread th_a
        // 同時啟動 timer1,timer1 會呼叫 timer1_Tick
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_a);

            th_a = thd;
            fgDone_a = false;
            guess_a = -1;
            thd.Start();
            timer1.Enabled = true;
        }

        // 呼叫 th_a.Abort()
        private void button2_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("使用Abort()中止執行緒\r\n");
            th_a.Abort();
        }

        // 呼叫 th_a.Interrupt()
        private void button3_Click(object sender, EventArgs e)
        {
            textBox1.AppendText("Interrupt()中斷執行緒\r\n");
            th_a.Interrupt();
        }

        // 用 count_b 產生 Thread th_b
        // 將全域變數 fgRun 設定為 true
        // 同時啟動 timer2,timer2 會呼叫 timer2_Tick
        private void button4_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(count_b);

            fgDone_b = false;
            guess_b = -1;
            fgRun = true;
            thd.Start();
            timer2.Enabled = true;
        }

        // 將全域變數 fgRun 設定為 false
        private void button5_Click(object sender, EventArgs e)
        {
            fgRun = false;
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            textBox2.AppendText(guess_b.ToString() + "\r\n");
            if (fgDone_b)
            {
                timer2.Enabled = false;
                if(!fgRun)
                    textBox2.AppendText("結束執行緒\r\n");
                else
                    textBox2.AppendText("找到了\r\n");
            }
        }

        // 關閉 Form1,要停止 th_a,fgRun 設定為 false
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if(th_a!=null)
                th_a.Abort();

            fgRun = false;
        }        
    }
}

Thead 存取表單控制項

Form UI 控制項是由 UI Thread 控制,如果自己產生的 Thread 要跨到 UI thread 存取控制項,會發生錯誤。

前面的例子,是在 Thread 裡面計算後,將過程記錄在全域變數中,然後在 UI Thread 以 Timer 定時將資料顯示在 UI 上,這種方法比較麻煩。

透過控制項的 InvokeRequired 屬性及 Invoke(),可讓 Thread 直接存取控制項。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {        
        delegate void SafeCall(string str);
        Thread []myThreads= { null, null };
        int sum = 0;

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl() //無參數
        {
            if (textBox1.InvokeRequired)
            {
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;
                MethodInvoker ivk = new MethodInvoker(safeControl);
                textBox1.Invoke(ivk, new object[] { });
            }
            else
                textBox1.AppendText("myThreads[1]: " + 
                    sum.ToString() + "\r\n");
        }

        void myFunc()
        {
            while (true)
            {                    
               safeControl();
               Thread.Sleep(500);
            }          
        }

        // 用 InvokeRequired 判斷 textBox1 存取權
        // 如果是 true,就表示現在是 UI thread 在控制
        // 就要由 textBox1 呼叫一次 SafeCall
        // false 就表示為外部 thread 存取,可直接使用 textBox1
        void safeControl_param(string str) //有參數
        {
            if (textBox1.InvokeRequired)
            {
                SafeCall ivk = new SafeCall(safeControl_param);
                textBox1.Invoke(ivk, new object[] { str });
            }
            else
                textBox1.AppendText(str + "\r\n");
        }

        // myThreads[0] 的 method
        // 呼叫 safeControl_param
        void myFunc_param()
        {
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);
                safeControl_param("myThreads[0]: " + num.ToString());
                Thread.Sleep(500);
            }                                
        }

        // 用 button1 產生 myThreads[0]
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd;

           // 如果 myThreads[0] 已經存在,就要停止 myThreads[0]
           // 用 myFunc_param 重新產生一個 myThreads[0],並啟動
           if (myThreads[0] != null)
            {
                myThreads[0].Abort();
                myThreads[0].Join();
            }
            thd = new Thread(new ThreadStart(myFunc_param));
            myThreads[0] = thd;

            thd.Start();
        }

        // 用 button2 產生 myThreads[1]
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[1] != null)
            {
                myThreads[1].Abort();
                myThreads[1].Join();
            }
            thd = new Thread(new ThreadStart(myFunc));
            myThreads[1] = thd;

            thd.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach(var item in myThreads)
                if (item != null)
                    item.Abort();           
        }        
    }
}

另一種寫法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread[] myThreads = { null, null };
        int sum = 0;

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl_param(string str) //有參數
        {
            textBox1.Invoke(new Action (() =>
            {
               textBox1.AppendText(str);
            }
            ));
        }

        // this.Invoke 配合 new Action 
        void myFunc_param()
        {
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);

                //也能寫成函式來來呼叫:safeControl_param(num.ToString());
                this.Invoke(new Action(() =>
                {
                    textBox1.AppendText("myThreads[0]: "+num.ToString()+"\r\n");
                }
             ));

            Thread.Sleep(500);
            }
        }

        void safeControl() //無參數
        {
            sum++;
            if (sum > int.MaxValue)
                sum = 0;

            this.Invoke((MethodInvoker)delegate 
            { textBox1.AppendText( "myThreads[1]: " +
                    sum.ToString() + "\r\n"); }
            );
        }

        void myFunc()
        {
            while (true)
            {
                ////此段程式也能寫成函式來來呼叫:safeControl();
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;

                this.Invoke((MethodInvoker)delegate
                {
                    textBox1.AppendText("myThreads[1]: " +
                          sum.ToString() + "\r\n");
                }
                );

                Thread.Sleep(500);
            }
        }


        // 有參數的 myFunc_param Thread
        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[0] != null)
            {
                myThreads[0].Abort();
                myThreads[0].Join();
            }
            thd = new Thread(new ThreadStart(myFunc_param));
            myThreads[0] = thd;

            thd.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (var item in myThreads)
                if (item != null)
                    item.Abort();
        }

        //  沒有參數的 myFunc Thread
        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd;

            if (myThreads[1] != null)
            {
                myThreads[1].Abort();
                myThreads[1].Join();
            }
            thd = new Thread(new ThreadStart(myFunc));
            myThreads[1] = thd;

            thd.Start();
        }
    }
}

第三種寫法

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        delegate void SafeCall(string str);
        Thread []myThread= { null, null };
        int sum;

        public Form1()
        {
            InitializeComponent();
        }

        void myFunc2()
        {
            MethodInvoker ivk = new MethodInvoker(safeControl);

            while (true)
            {
                sum++;
                if (sum > int.MaxValue)
                    sum = 0;
                this.Invoke(ivk, new object[] { });
                Thread.Sleep(500);
            }
        }

        void safeControl() //無參數
        {
            textBox1.AppendText(sum.ToString() + "\r\n");
            label1.Text = sum.ToString() ;

        }

        void myFunc1()
        {
            SafeCall ivk = new SafeCall(safeControl_param);
            Random rd = new Random();
            int num;

            while (true)
            {
                num = rd.Next(1, 101);
                this.Invoke(ivk, new Object[] { num.ToString()+"\r\n" });
                Thread.Sleep(500);
            }
        }

        private void safeControl_param(string str)
        {
            textBox1.AppendText(str);
            label1.Text = str;

        }

        private void button1_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(myFunc1);
            myThread[0] = thd;

            thd.Start();

        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (var item in myThread)
                if (item != null)
                    item.Abort();
        }

        private void button2_Click(object sender, EventArgs e)
        {
            Thread thd = new Thread(myFunc2);
            myThread[1] = thd;

            sum = 0;
            thd.Start();
        }
    }
}

執行緒同步

如果有多個 thread 會同時存取相同的資源,會造成內容一致性的問題。有四個方法,可處理 synchronization 問題

  • synchronized code region -> 最常用

    當 thread A 執行時,thread B 必須等待 A 完成後,才能執行該區塊

  • manual synchronization

    使用 .Net Framework 的 Mutex, Semaphore, EventWaitHandle, AutoResetEvent, ManualResetEvent

  • synchronized contexts

    使用 SynchronizationAttribute 設定 ContextBoundObject

    當物件進入或離開由 ContextBoundObject 定義的內容時,會強制執行規則

  • 使用 System.Collections.Concurrent 的集合與類別

Critical Section: thread 存取的共用資源(物件、資料、變數、設備),同一時間只有一個 thread 能使用

lock()

互斥鎖: public object locker = new object();

lock(locker) 以 locker 鎖定 critical section

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread thd1=null, thd2=null;
        object locker = new object();
        bool fg1 = false, fg2=false;

        public Form1()
        {
            InitializeComponent();
        }

        // 當 Form1 載入時,就啟動兩個 thread
        private void Form1_Load(object sender, EventArgs e)
        {
            thd1 = new Thread(func1);
            thd2 = new Thread(func2);

            thd1.Start();
            thd2.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (thd1 != null)
                thd1.Abort();

            if (thd2 != null)
                thd2.Abort();
        }

        // 無窮迴圈
        // 當 fg1 為 true,就 lock locker 物件,執行 critical section 區塊
        // 完成後將 fg1 改為 false
        void func1()
        {
            while(true)
            {
                if(fg1)
                    lock(locker)
                    {
                        for(int i=0;i<10;i++)
                        {
                            textBox1.Invoke(new Action(() =>
                            {
                                textBox1.AppendText("Thread 1\r\n");
                            }));
                            Thread.Sleep(500);
                        }
                        fg1 = false;
                    }
            }
        }

        void func2()
        {
            while (true)
            {               
                if (fg2)
                    lock (locker)
                    {
                        textBox1.Invoke(new Action(() =>
                        {
                            textBox1.AppendText("Thread 2\r\n");
                        }));
                        fg2 = false;
                    }
            }        
        }

        // 將 fg1 改為 true
        private void button1_Click(object sender, EventArgs e)
        {                        
            if (!fg1)
                fg1 = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!fg2)
                fg2 = true;
        }     
    }
}

Monitor

method 說明
Enter(a, [b]) 取得並鎖定互斥鎖 a,a為 Object,b 為 Boolean。當 b 為 true,表示已經取得互斥鎖 a,否則為 false
Exit(a) 釋放互斥鎖 a
IsEntered(a) 判斷 thread 是否已經取得互斥鎖 a
Pulse(a) 通知等候的 thread,互斥鎖 a 已改變狀態
PulseAll(a) 通知等候 queu 的所有 threads,互斥鎖 a 已改變狀態
TryEnter(a,b,c) 嘗試在時間 b 取得互斥鎖 a,並回傳結果 c。
a: Object, b: Int32 or TimeSpan, c: Boolean
TryEnter(a[,b]) 嘗試在時間 b 取得互斥鎖 a。
a: Object, b: Int32 or TimeSpan
TryEnter(a[,b]) 嘗試取得互斥鎖 a,並回傳結果 b。
a: Object, b: Boolean
Wait(a[,b[,c]]) 釋放互斥鎖 a ,並在時間 b 內,嘗試重新取得互斥鎖 a。如果無法取得,就會進入等候 queue
a: Object, b: Int32 or TimeSpan, c: Boolean

Enter, Exit 要搭配使用,否則會造成 a 無法釋放或 deadlock

Pulse() 只能被正在鎖定互斥鎖的 thread 呼叫

critical section

Monitor.Enter(locker);
.
.
.
Monitor.Exit(locker);
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Thread thd1 = null, thd2 = null;
        object locker = new object();
        bool fg1 = false, fg2 = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            thd1 = new Thread(func1);
            thd2 = new Thread(func2);

            thd1.Start();
            thd2.Start();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (thd1 != null)
                thd1.Abort();

            if (thd2 != null)
                thd2.Abort();
        }

        void func1()
        {
            while (true)
            {
                if (fg1)                    
                {
                    Monitor.Enter(locker);
                    for (int i = 0; i < 10; i++)
                    {
                        textBox1.Invoke(new Action(() =>
                        {
                                textBox1.AppendText("Thread 1\r\n");
                        }));
                        Thread.Sleep(500);
                    }
                    Monitor.Exit(locker);
                    fg1 = false;
                }
            }
        }

        void func2()
        {
            while (true)
            {
                if (fg2)                    
                {
                    Monitor.Enter(locker);
                    textBox1.Invoke(new Action(() =>
                    {
                        textBox1.AppendText("Thread 2\r\n");
                    }));
                    Monitor.Exit(locker);
                    fg2 = false;
                }
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (!fg1)
                fg1 = true;
        }

        private void button2_Click(object sender, EventArgs e)
        {
            if (!fg2)
                fg2 = true;
        }
    }
}

Semaphore

5 人去店裡繳費,但只有 3 個櫃檯

一般來說臨櫃劉成為:取號碼牌、有空的櫃檯時叫號、沒有空的櫃檯就等待

Semaphore 提供機制協調多個 thread 的同步處理

// 有三個號誌數量,最多可接受3個 thread 要求號誌 
Semaphore smphore = new Semaphore(3,3);


// 有0個號誌數量,最多可接受3個 thread 要求號誌
// 一開始,所有 thread 都在等待 
Semaphore smphore = new Semaphore(0,3);

// 釋放 3 個號誌
smphore.Release(3);

// 沒有參數,表示釋放先前取得的號誌
smphore.Release();
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        Semaphore smphore;
        Thread[] thds=new Thread[5];
        delegate void SafeCall(string str);

        public Form1()
        {
            InitializeComponent();
        }

        void safeControl(string str)
        {
            textBox1.AppendText(str);
        }

        void func(object param)
        {
            int no = (int)param;
            string str;
            SafeCall ivk = new SafeCall(safeControl);

            str = String.Format("第{0}位在排隊...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str});

            // 所有 threads 會停在這裡等待 semaphore
            smphore.WaitOne();

            str = String.Format("第{0}位正在繳費...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str });
            Thread.Sleep(1000);

            str = String.Format("第{0}位繳費結束...\r\n", no);
            textBox1.Invoke(ivk, new Object[] { str });
            smphore.Release();
            // 結束時,釋放 semaphore
        }        

        private void Form1_Load(object sender, EventArgs e)
        {
            // 載入時,產生 semaphore
            smphore = new Semaphore(0, 3);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // 產生並啟動 5 個 threads
            for (int i = 0; i < thds.Length; i++)
                thds[i] = new Thread(
                    new ParameterizedThreadStart(func));

            for (int i = 0; i < thds.Length; i++)
                thds[i].Start(i + 1);

            textBox1.AppendText("尚未開始營業...\r\n");
            Thread.Sleep(3000);

            textBox1.AppendText("開始營業...\r\n");
            smphore.Release(3);
        }
    }
}

References

博客來-C#程式設計從入門到專業(下):職場C#進階應用技術

2022/06/06

acmesh

因原本使用的 Let's Encrypt 提供的 certbot,在舊版的 CentOS 7 會遇到 python 及 kernel 更新的問題,現在改用 GitHub - acmesh-official/acme.sh: A pure Unix shell script implementing ACME client protocol 做免費的 SSL 憑證。

安裝,會安裝到 /root/.acme.sh

curl https://get.acme.sh | sh -s email=charley@maxkit.com.tw

設定自動更新 acme.sh

acme.sh --upgrade --auto-upgrade

在 crontab 可發現自動更新的 script

# crontab -e
59 0 * * * "/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null

申請 zone.larzio.com 的憑證

acme.sh --issue -d zone.larzio.com --webroot /var/www/html/

申請成功後,憑證相關檔案會在

  • 憑證 /root/.acme.sh/zone.larzio.com/zone.larzio.com.cer

  • key /root/.acme.sh/zone.larzio.com/zone.larzio.com.key

  • intermediate ca cert /root/.acme.sh/zone.larzio.com/ca.cer

  • full chain certs /root/.acme.sh/zone.larzio.com/fullchain.cer

將憑證複製給 haproxy 使用: /etc/haproxy/ssl/server.pem

acme.sh --installcert -d zone.larzio.com \
 --cert-file /etc/haproxy/ssl/zone.larzio.com.cer \
 --key-file /etc/haproxy/ssl/zone.larzio.com.key \
 --fullchain-file /etc/haproxy/ssl/zone.larzio.com.fullchain.cer \
 --reloadcmd "cat /etc/haproxy/ssl/zone.larzio.com.cer /etc/haproxy/ssl/zone.larzio.com.key | tee /etc/haproxy/ssl/server.pem"

References

HAProxy 實現 h2 到 h2c 的解析 - 台部落

Centos7 下使用acme.sh以DNS方式申請免費SSL證書 - 台部落